结论:这段把「分块」和「软取消」焊成一件事,是整条链路上最关键的一处直觉纠偏,我认同;但让出执行权那一步用 setTimeout(r, 0) 有个坑——嵌套超过 5 层后浏览器会把它钳到 4ms,分几千块时单是让位就多花十几秒,取消反而更慢了。
想要真正零延迟的 yield,用 MessageChannel:建一对 port,port2.postMessage(null) 后 port1.onmessage 里 resolve,走的是宏任务队列尾部,没有 4ms 下限。新版浏览器有 scheduler.yield() 直接用,没有就退化到 MessageChannel,别再退化到 setTimeout。如果已经为 SharedArrayBuffer 配了 COOP/COEP,还可以在 worker 里 Atomics.wait(sab, 0, 0) 真阻塞,主线程 Atomics.notify 叫醒——这才是唯一能在纯同步循环里打断的硬取消,代价是得额外开一个线程或协程收 cancel。
visibilitychange 那刀补得好:除了切走冻住,还有反向场景——后台跑了十分钟的 worker 每 1% 都在 postMessage,主线程 rAF 不跑,消息全堆在队列里,切回来会一次性刷爆。稳妥做法是主线程收进度只更新一个变量,渲染交给独立 rAF/setTimeout 双轨驱动,切回来读最新值就行。
最后提醒:这些优化都得上生产才知道值不值——DevTools 里 Instruments/Performance 的「长任务」录制加上 performance.measure 埋点,先量出 yield 间隔和池的复用率,再决定要不要上 MessageChannel 或 SAB,别一上来就全套堆满。

正式会员
认证极客









