JavaScript的宏任务和微任务

JavaScript 是一个单线程的编程语言。为了能够处理异步任务(如定时器、I/O),它通过事件循环(event loop)来调度任务的执行。JavaScript 中存在两种异步任务:宏任务(macrotask)和微任务(microtask)。它们有各自的队列。

常见的宏任务包括:

  • 脚本的执行
  • setTimeout() 的回调
  • setInterval() 的回调
  • 用户事件回调
  • I/O 回调

而常见的微任务包括:

  • Promise.then() 的回调
  • Promise.catch() 的回调
  • Promise.finally() 的回调
  • await 之后的代码
  • queueMicrotask() 的回调
  • 浏览器中的 MutationObserver 的回调

new Promise() 的回调并不是一个微任务,而是同步的。

事件循环可以理解为一个在“执行任务”和“等待任务”两个状态之间无限转换的状态机。它的工作流程可以简化地描述为:

  1. 执行一个宏任务
  2. 执行微任务,直到微任务队列被清空
  3. 如果宏任务队列为空,等待
  4. 重复步骤 1

如果一个微任务创建了新的微任务,新的微任务会立刻被添加至队列末端,并在下一个宏任务之前被执行。

接下来,用几段代码来举例说明一下。

1
2
3
4
5
6
7
8
9
10
console.log(1);
new Promise((resolve) => {
console.log(2);
resolve();
}).then(() => {
console.log(3);
});
console.log(4);

// 1 2 4 3
  1. 脚本开始执行,即当前宏任务
  2. console.log(1) 是同步任务,立刻输出 1
  3. new Promise() 中的回调也是同步任务,立刻输出 2
  4. .then() 中的回调被添加到微任务队列,此时微任务队列为 () => console.log(3)
  5. console.log(4) 是同步任务,立刻输出 4
  6. 当前宏任务执行完毕,开始执行微任务队列(() => console.log(3)),输出 3
  7. 此时微任务队列为空
  8. 此时宏任务队列也为空,结束
1
2
3
4
5
6
7
8
9
10
11
12
async function run() {
console.log(1);
await new Promise((resolve) => {
console.log(2);
resolve();
});
console.log(3);
}
run();
console.log(4);

// 1 2 4 3
  1. 脚本开始执行,即当前宏任务
  2. console.log(1) 是同步任务(虽然它在 async 函数中),立刻输出 1
  3. new Promise() 中的回调也是同步任务,立刻输出 2
  4. 不管接收到的是不是 Promiseawait 都会立刻暂停 async 函数的执行,并在等待的值 fulfilled 将后续代码添加到微任务队列。由于这里的 Promise 已经同步 resolve,此时微任务队列为 () => console.log(3)
  5. console.log(4) 是同步任务,立刻输出 4
  6. 当前宏任务执行完毕,开始执行微任务队列(() => console.log(3)),输出 3
  7. 此时微任务队列为空
  8. 此时宏任务队列也为空,结束
1
2
3
4
5
6
7
8
9
console.log(1);
setTimeout(() => console.log(2));
Promise.resolve().then(() => console.log(3));
Promise.resolve().then(() => setTimeout(() => console.log(4)));
Promise.resolve().then(() => Promise.resolve().then(() => console.log(5)));
setTimeout(() => console.log(6));
console.log(7);

// 1 7 3 5 2 6 4
  1. 脚本开始执行,即当前宏任务
  2. console.log(1) 是同步任务,立刻输出 1
  3. setTimeout() 的回调是宏任务,此时宏任务队列为 () => console.log(2)
  4. 几个 .then() 的回调都是微任务,此时微任务队列为 () => console.log(3)() => setTimeout(() => console.log(4))Promise.resolve().then(() => console.log(5))
  5. setTimeout() 的回调是宏任务,此时宏任务队列为 () => console.log(2)() => console.log(6)
  6. console.log(7) 是同步任务,立刻输出 7
  7. 当前宏任务执行完毕,开始执行微任务队列(() => console.log(3)() => setTimeout(() => console.log(4))() => Promise.resolve().then(() => console.log(5))
  8. console.log(3) 立刻输出 3
  9. setTimeout() 又注册了新的宏任务,此时宏任务队列为 () => console.log(2)() => console.log(6)() => console.log(4)
  10. 在执行第三个微任务时,.then() 又把微任务添加到了队列,因此此时微任务队列还是不为空,为 () => console.log(5)
  11. console.log(5) 立刻输出 5
  12. 此时微任务队列为空,开始执行宏任务 () => console.log(2),立刻输出 2
  13. 此时微任务队列依旧为空,开始执行宏任务 () => console.log(6),立刻输出 6
  14. 此时微任务队列依旧为空,开始执行宏任务 () => console.log(4),立刻输出 4
  15. 此时宏任务队列为空,结束

参考资料


JavaScript的宏任务和微任务
https://tomzhu.site/2026/06/28/JavaScript的宏任务和微任务/
作者
Tom Zhu
发布于
2026年6月28日
许可协议