微任务:在「下一个宏任务」之前全部执行完
微任务 (microtask)
是一类优先级更高的小任务:当前这段代码执行完(调用栈清空)后,引擎立即把微任务队列清空到底——清空过程中新产生的微任务也算这一轮,要一并清完——之后才会去渲染或取下一个宏任务。产生微任务的只有三种途径:queueMicrotask()、Promise.then/catch/finally(含
await 之后的代码)、MutationObserver 的回调。本页全部真实执行,演示它们的三个特性:优先执行、批处理、可能阻塞渲染。
1 · 微任务全部先于宏任务执行
下面先排一个 setTimeout(宏),再排一条 5 连环的微任务链(每个微任务中再排下一个)。看谁先执行完:
2 · MutationObserver:同步改 N 次 DOM,回调只攒成一次
MutationObserver 不是改一次 DOM 就回调一次。它把一轮同步代码里的所有改动攒成一批,在本轮微任务阶段一次性把所有记录交给你的回调——这正是「微任务在宏任务边界统一结算」的体现。
3 · 微任务可能阻塞渲染 (starvation)
既然微任务必须清空到底才渲染,那么一条足够长的微任务链就能把渲染和宏任务无限期推后。下面排一条 N 节的微任务链,每节都把屏幕上的计数器 +1 写入 DOM,同时排一个 setTimeout。注意观察:
结果:计数器从 0 直接跳到终点,中途没有任何重绘——因为微任务未清空,浏览器没有渲染机会。如果这条链永不结束(例如 queueMicrotask 中无条件再调用 queueMicrotask),页面会完全冻结:点击、动画、乃至关闭标签页的提示都无响应。这是真实的故障模式,称为 microtask starvation。
那么 await 呢?await x 本质是 Promise.resolve(x).then(后续代码)——await 之后的每一行都被打包成一个微任务。因此 async 函数中一旦 await,后续逻辑就让到了微任务阶段,早于 setTimeout、早于渲染。queueMicrotask
则是「不需要 Promise,只需要一个微任务时机」的直接入口。