Scheduler.postTask:给宏任务装上「优先级」
setTimeout 的局限是没有优先级:一个上报埋点和一个响应用户点击,排入队列后同等对待,先到先执行。scheduler.postTask() 解决了这一点——每个任务带一个优先级 (user-blocking / user-visible /
background),浏览器按优先级调度,还能中途调整优先级、取消,并与输入 / 渲染统一排程。这是 W3C Scheduling APIs 的一部分,可理解为「带优先级的 setTimeout」。
1 · 三档优先级
2 · 乱序排入,按优先级执行
下面按源码顺序把 6 个任务一次性 postTask 排入(优先级混合),观察它们实际的执行顺序:
规则:跨优先级看挡位 (user-blocking → user-visible → background),同优先级内 FIFO。所以「响应输入」总能插到「上报埋点」前面,哪怕埋点的代码写在更前面。这正是 setTimeout 给不了的。
3 · 还能取消 (TaskController)
postTask 配一个 TaskController:把它的 signal 传进去,任务跑之前调 controller.abort() 就能撤销(返回的 promise 以 AbortError 拒绝)。同一个 controller 还能 setPriority() 动态调档。
4 · 和老办法对比
| setTimeout(fn,0) | requestIdleCallback | scheduler.postTask | |
|---|---|---|---|
| 优先级 | 无 | 只有「空闲」一档 | 三档,可动态改 |
| 可取消 | clearTimeout | cancelIdleCallback | AbortController 统一 |
| 最小延时 | ≥4ms (嵌套) | 不定 | 无人为下限 |
| 后台限流 | ≥1s | — | 否 |
| 返回值 | 定时器 id | 回调 id | Promise (能 await) |
| 浏览器支持 | 全部 | 除 Safari | 较新,需 polyfill |
配套 API:scheduler.yield()。在一个长任务中途 await scheduler.yield(),会让出主线程给更紧急的输入 / 渲染,随后仍按原优先级继续执行——优于手动 await new Promise(r => setTimeout(r))(yield 返回后优先于新排入的同类任务,不会被挤到队尾)。时间切片的现代写法依赖于它,应用页将继续讨论长任务与 INP。