微任务执行结束后,事件循环先检查是否需渲染(仅浏览器),确认后才取下一个宏任务;当前宏任务结束必清空全部微任务(含动态新增),队列为空才算结束;渲染后按FIFO取宏任务,setTimeout(0)并非立刻执行。
微任务执行结束后,事件循环不会立刻挑下一个宏任务,而是先做一次环境检查:浏览器会判断是否需要渲染(比如 dom 有变更),node.js 则跳过这步;确认渲染完成(或无需渲染)后,才从宏任务队列头部取出一个任务开始执行。
微任务清空是硬性前提
只要当前宏任务结束,引擎就会强制清空整个微任务队列——包括执行中动态产生的新微任务。这个过程不中断、不插队、不等待。只有队列彻底为空,才算“微任务执行结束”。
渲染环节可能介入(仅浏览器)
微任务队列空了之后,浏览器会检查本轮是否有 DOM 变更(例如 innerHTML 修改、class 切换、样式更新等)。如果有且页面可见,通常会安排一次 UI 渲染;这个动作不是强制的,由浏览器根据帧率、节流策略决定。Vue 的 nextTick 和 React 的 flushSync 就是基于这个时机设计的。
下一个宏任务从队列头取出
渲染完成后(或跳过渲染),事件循环回到宏任务队列,严格按先进先出(FIFO)顺序取一个任务:
- 可能是 setTimeout/setInterval 回调(哪怕设为 0,也得等本轮彻底收尾)
- 可能是用户交互触发的事件处理器(如 click、input)
- 可能是网络请求(fetch、XMLHttpRequest)响应后推入的回调
- 也可能是 postMessage 或 MessageChannel 发来的消息事件
注意几个常见误区
setTimeout(0) 不是“立刻” —— 它只是尽快排进宏任务队列尾部,实际执行必须等当前宏任务 + 所有微任务 + (可能的)渲染全部完成。
宏任务之间不共享微任务队列 —— 每个宏任务结束时,只清空“当前轮次”积累的微任务,不会处理上一轮遗留或下一轮提前插入的。
async/await 后续代码属于微任务 —— await 解析完 Promise 后的语句,本质是被包装进 Promise.then,所以也遵循微任务调度规则。

















