微任务总在宏任务结束后立即执行:每个宏任务执行完毕后,必须清空全部微任务队列,之后才取下一个宏任务;promise.then、queuemicrotask等微任务必先于settimeout等宏任务执行。

微任务总在宏任务结束后立即执行
微任务不是“插队”,而是事件循环的硬性规则:每个宏任务执行完,必须清空全部微任务队列,一个都不能剩,之后才允许取下一个宏任务。这导致 Promise.then、queueMicrotask、MutationObserver 的回调,哪怕写在 setTimeout 里面,也一定比下一个 setTimeout 先跑。
常见错误:以为 setTimeout(fn, 0) 比 Promise.then 快
这是最典型的误解。实际运行时:
-
setTimeout(() => console.log('timeout'), 0)进入宏任务队列 -
Promise.resolve().then(() => console.log('promise'))进入微任务队列 - 当前宏任务(比如脚本主流程)结束 → 立即执行
promise - 微任务队列清空 → 浏览器可能渲染 → 再从宏任务队列取最老的,执行
timeout
所以输出永远是 promise 先于 timeout,哪怕时间设为 0。
Node.js 中 process.nextTick 是特例
process.nextTick 不是标准微任务,但优先级比 Promise.then 还高 —— 它会在当前操作(比如一次回调)结束后、任何微任务之前执行。这意味着:
- 同一轮中,
process.nextTick→Promise.then→setTimeout - 它不被
queueMicrotask或 Promise 规范覆盖,是 Node.js 独有的调度钩子 - 滥用会导致 I/O 饥饿,因为每次 nextTick 都会抢占微任务执行权
浏览器里 requestAnimationFrame 属于宏任务,但时机特殊
requestAnimationFrame 虽然归类为宏任务,但它不进普通宏任务队列,而是由浏览器在下一次重绘前统一调度。它的实际执行时机:
- 在当前宏任务 + 所有微任务执行完后
- 但在浏览器渲染帧开始前(通常约 16ms 一帧)
- 如果连续调用,只保留最后一次,避免重复排队
所以它既不是“最快”的异步方式,也不是普通定时器,不能靠它替代 Promise 做逻辑顺序控制。
真正容易被忽略的是:微任务队列不是“先进先出”就完事了,而是在每个宏任务出口处强制清空 —— 这个“强制”意味着你无法用任何手段打断或延迟它;一旦进了微任务队列,就注定在这轮循环末尾执行。











