宏任务与微任务按“一个宏任务→清空全部微任务→下一个宏任务”严格执行;每轮仅处理一个宏任务,其后立即连续执行所有微任务直至队列为空,新微任务也纳入本轮;setTimeout等宏任务须等当前宏任务及所有微任务完成后才执行。
宏任务与微任务不是“交替”执行,而是严格遵循一个宏任务 → 清空全部微任务 → 下一个宏任务的固定节奏。这个顺序不会跳步、不会穿插,每轮只推进一次宏任务,但会把当轮产生的所有微任务一口气执行完。
每次事件循环只处理一个宏任务
宏任务由浏览器或 Node.js 环境调度,每次事件循环最多取一个出来执行。常见来源包括:
- 整个
<script></script>脚本(初始宏任务) -
setTimeout和setInterval回调 - 用户交互事件(如
click、input) - 网络请求完成回调(
fetch、XMLHttpRequest) - UI 渲染(浏览器中,在宏任务后、下一轮前触发)
宏任务结束后立刻清空微任务队列
只要当前宏任务执行完毕、调用栈清空,JS 引擎就会立即检查微任务队列,并连续执行直到队列为空。这个过程不中断、不暂停,新加入的微任务也会被纳入本轮执行。典型微任务有:
-
Promise.then/catch/finally回调 -
async/await中await后面的代码(等价于.then) -
queueMicrotask()显式添加的任务 -
MutationObserver回调(浏览器中)
微任务不会触发新事件循环,但会延迟下一个宏任务
微任务执行期间,不会启动新一轮事件循环,也不会触发 UI 渲染。它只是“插在”两个宏任务之间,抢占执行权。这意味着:
- 即使嵌套多个
Promise.then,它们都属于同一轮微任务,会链式执行完 -
setTimeout即使设为 0ms,也必须等当前宏任务 + 所有微任务全部结束,才能排到下一轮 - 如果微任务里又注册了新的微任务(比如再调一次
Promise.resolve().then),它会被加到当前队列末尾,仍在本轮执行
真实执行流程示例
看这段代码:
console.log(1);setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
Promise.resolve().then(() => {
console.log(4);
setTimeout(() => console.log(5), 0);
});
console.log(6);
输出顺序是 1 → 6 → 3 → 4 → 2 → 5。原因很清晰:
- 同步代码
console.log(1)和console.log(6)立即执行 - 两个
Promise.then进入微任务队列,宏任务一结束就连续执行,输出 3 和 4 - 嵌套的
setTimeout在输出 4 后才注册,所以它的回调排在下一个宏任务位置,输出 2 和 5










