前端 `Scheduler` 任务调度器 应用场景有哪些?

今天看一篇老外的性能优化文章

https://web.dev/optimize-long-tasks/#a-dedicated-scheduler-api

偶然 看到了 Scheduler

MDN 上的介绍
https://developer.mozilla.org...

大概意思就是可以 调度用户代码的优先级?

但是 实在想不到 有哪些应用场景, 比如我两个函数

const fnA = ()=>{console.log('aaa')}
const fnB = ()=>{console.log('bbb')}

想要 先执行 fnA 再执行 fnB

fnA()
fnB()

这样不就好了, 还是我对 这个 调度器 理解有误?

恳请 大佬解惑!

阅读 5.2k
2 个回答

首先明确一点,对于原本不可控,现在需要控制的流程,才需要引入 调入机制

  • 对于 同步任务,调整代码顺序就能控制,可以直接排除无需讨论
  • 对于将要执行的 异步任务,可能在某些场景下,你想要后排入的 异步任务 先执行,这种场景下才需要 调度。

    • 那么就有了第一个问题,如何调度,按照什么规则调度 ?这里就需要引入 优先级 的概念,在发起 异步任务 时,为该任务分配一个 优先级,后续 调度器 进行任务调度时,先执行谁,后执行谁,才有依据

这里我们假设一个需求场景

// main.js

// 先派发一个 低优先级 的耗时异步任务:请求第三方配置信息
scheduler.postTask(() => console.log('低优耗时任务:请求第三方配置信息'), {
    priority: 'background',
});
// 执行一段耗时的同步代码阻塞主线程:模拟系统初始化
(() => {
   console.time('init')
   for(let i = 0; i < 10000000000; i++) {}
   console.timeEnd('init')
})()

// 假设此时用户点击了一个按钮,按钮事件触发的 逻辑 需要在 系统初始化完后 优先被响应
document.querySelector('userButton').click()

// ------------------------------------------------------
// event.js

// 在事件注册的位置,我们可以派发 高优先级的 异步任务,来保证 其先于 低优先级任务 被调度执行
document.querySelector('userButton').addEventListener('click', () => {
  scheduler.postTask(() => console.log('高优先级任务:用户交互'), {
      priority: 'user-blocking',
  });
})

调度机制 除了 可指定任务优先级的能力 之外,还具备 取消任务 的能力。同样这里假设一个 页面切换 的场景:

  • 有两个页面,加载每个页面会渲染 1000 个组件
  • 在对页面进行频繁切换时,就可能会出现性能问题,原因是进入一个页面时,会开启渲染组件的流程,这是一个耗时任务。假设这时切换页面,由于 主线程 还在进行上一个页面的组件渲染,就会出现卡顿,页面无法响应的现象

对于这种场景,我们可以把 每个组件的渲染 拆分开,交由调度器去调度

const tasks = [];
// 渲染组件
components.forEach((component) => {
    const abortTaskController = new TaskController();
    tasks.push(abortTaskController)
    // 每个组件的渲染 交给调度器
    scheduler.postTask(() => component.init(), {
        priority: 'user-visible',
        signal: abortTaskController.signal
    });
})

// 当切换页面时, 取消之前已经 派发 但还未执行的任务
tasks.forEach((task) => task.abort())

注意:以上代码均不完整,仅作演示

最后,有兴趣可以了解下 react scheduler,这是 react 实现的调度器,或许可以帮助你走进 认识调度器 的大门

调度优先级说的是异步任务优先级

scheduler.postTask 返回的是 Promise

以往我们在浏览器需要实现异步任务优先级需要靠以下 API(仅常用不是全部)

  • Promise
  • setTimeoutsetInterval
  • requestIdleCallback
  • requestAnimationFrame

测试执行顺序(微任务优先级)

setTimeout(() => console.log('setTimeout'), 0)
Promise.resolve().then(() => console.log('Promise'))
requestIdleCallback(() => console.log('requestIdleCallback'))
requestAnimationFrame(() => console.log('requestAnimationFrame'))
console.log('sync')

输出

sync
Promise
setTimeout
requestAnimationFrame
requestIdleCallback

常见用途

避免同步执行的代码堵塞 DOM 渲染导致感官上的页面卡顿

  • 接口请求一般用 Promise
  • 渲染每帧画面(游戏引擎主循环)可能需要 requestAnimationFrame
  • 上报日志/预处理数据可能需要 requestIdleCallback

由于上报日志的 requestIdleCallback 优先级在 Promise 之后,所以也不会影响接口请求

结论

应该 scheduler 主要还是作为这部分的标准把

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题