微任务总在宏任务之后、下一个宏任务之前执行,不是竞争而是硬性节奏:同步代码→清空微任务队列→执行一个宏任务→再清空微任务队列→循环;promise.then、queuemicrotask等属微任务,settimeout、dom事件、ui渲染等属宏任务,requestanimationframe为渲染特例。

微任务总在宏任务之后、下一个宏任务之前执行,这不是竞争,而是事件循环硬性规定的执行节奏——每次只取一个宏任务,但必须清空全部微任务才能继续。
微任务和宏任务到底谁“先”?
不是谁抢到谁先,而是有固定顺序:同步代码 → 微任务队列清空 → 一个宏任务 → 微任务队列清空 → … 循环往复。比如下面这段代码:
- 同步部分:console.log('start') 和 console.log('end') 立即输出
- 微任务部分:Promise.then 回调被加入微任务队列,同步代码一结束就执行
- 宏任务部分:setTimeout 回调排在下一轮事件循环,等所有微任务跑完才轮到它
所以输出一定是 start → end → promise → setTimeout,不会因为 setTimeout 写在 Promise 前面就先执行。
哪些操作属于微任务?哪些算宏任务?
区分关键看“谁添加”和“何时调度”:
- 微任务(JS 引擎内部调度,高优先级):Promise.then/catch/finally、queueMicrotask()、MutationObserver 回调
- 宏任务(宿主环境调度,每次只执行一个):setTimeout/setInterval、script 整体、DOM 事件(click)、fetch 回调、UI 渲染(浏览器中)
注意:requestAnimationFrame 是个特例——它属于渲染阶段的一部分,既不是标准微任务,也不完全等同于 setTimeout,常用于需要对齐屏幕刷新节奏的场景。
为什么设计成“微任务必须清空”?
这是为了保证异步逻辑的可预测性:
- Promise 链式调用能连续执行,避免中间被 UI 渲染或定时器打断
- 状态更新(如 React 的 setState 批处理)可以聚合成一次响应,而不是分散触发多次 render
- 错误兜底逻辑(如 catch 后的清理)能及时运行,不被后续宏任务延迟
但如果在微任务里反复注册新微任务(比如无限 queueMicrotask),就会导致宏任务永远没机会执行,界面卡死——这就是“事件循环饥饿”。
怎么选?按意图,不按快慢
别只盯着“微任务更快”,要匹配实际需求:
- 需要确保 DOM 已更新、但还没渲染?→ 用
queueMicrotask - 需要等浏览器完成绘制、再读取布局尺寸?→ 用
requestAnimationFrame - 想把耗时计算延后、又不阻塞交互?→ 用
requestIdleCallback或 Web Worker - 真要等网络或用户输入?→ 就老实用
fetch或addEventListener,它们本来就是宏任务源头











