Skip to content

调度、idle 与优先级 ​

调度、idle 与优先级

机制拆解 ​

调度 API 只改变任务进入队列的方式,不会把同步函数自动变成后台线程。先用一个定义完整的任务比较支持路径与降级路径:

html
<output id="output">等待任务</output>
<script>
  const output = document.querySelector('#output')
  const task = () => { output.value = 'background task ran' }
  if ('scheduler' in globalThis && 'postTask' in globalThis.scheduler) {
    globalThis.scheduler.postTask(task, { priority: 'background' })
  } else {
    setTimeout(task, 0)
  }
</script>

记录按钮点击、任务执行和页面绘制的顺序;把 priority 改为 user-visible,只能比较队列优先级的观察结果,不能推断它改变了 CPU 执行时间。

边界与失败路径 ​

  • 所有权:监听器、observer、worker、连接、缓存和焦点都有创建者与释放时机;页面隐藏、路由离开和元素移除时都要验证。
  • 宿主差异:SSR、Web Worker、Shadow DOM、跨文档、跨源和辅助技术不会共享同一套全局对象与事件路径。
  • 错误分类:取消、超时、权限拒绝、协议错误、配额不足和浏览器不支持应分别进入可观察状态。
  • 安全边界:用户输入、URL、HTML、消息和持久化数据不能仅因为来自同源或类型正确就视为可信。

可运行实验 ​

把下面页面另存为 scheduler-lab.html,点击按钮 30 次。记录控制台中 scheduled 与 ran 的顺序;再把 scheduler.postTask 分支临时改为 setTimeout,比较顺序是否稳定。

html
<button id="run">排队任务</button><output id="output"></output>
<script>
  const output = document.querySelector('#output')
  const run = () => {
    output.value += ' ran'
    console.log('ran')
  }
  document.querySelector('#run').addEventListener('click', () => {
    output.value += ' scheduled'
    console.log('scheduled')
    if ('scheduler' in globalThis && 'postTask' in globalThis.scheduler) globalThis.scheduler.postTask(run, { priority: 'background' })
    else setTimeout(run, 0)
  })
</script>

实验记录要写明浏览器是否支持 scheduler.postTask;只比较任务进入回调的顺序,不把一次观察推导成所有设备上的性能结论。

练习 ​

  1. 解释一次点击中 scheduled 先于 ran 的原因,并说明 setTimeout 降级保留了什么契约。
  2. 连续点击后刷新页面,说明页面卸载时尚未执行的任务是否还能更新 DOM,以及如何取消自己创建的任务。
  3. 将优先级改为 user-visible,写出仍然不能由该实验证明的性能结论。

查看本节答案

下一步 ​

完成练习后回到本章概览,按下一篇继续;遇到跨章节故障,先回到本节的所有权与宿主边界。

用心记录每一步成长