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
如果一个微任务创建了新的微任务,新的微任务会立刻被添加至队列末端,并在下一个宏任务之前被执行。
接下来,用几段代码来举例说明一下。
1 | |
- 脚本开始执行,即当前宏任务
console.log(1)是同步任务,立刻输出1new Promise()中的回调也是同步任务,立刻输出2.then()中的回调被添加到微任务队列,此时微任务队列为() => console.log(3)console.log(4)是同步任务,立刻输出4- 当前宏任务执行完毕,开始执行微任务队列(
() => console.log(3)),输出3 - 此时微任务队列为空
- 此时宏任务队列也为空,结束
1 | |
- 脚本开始执行,即当前宏任务
console.log(1)是同步任务(虽然它在async函数中),立刻输出1new Promise()中的回调也是同步任务,立刻输出2- 不管接收到的是不是
Promise,await都会立刻暂停async函数的执行,并在等待的值 fulfilled 将后续代码添加到微任务队列。由于这里的 Promise 已经同步 resolve,此时微任务队列为() => console.log(3) console.log(4)是同步任务,立刻输出4- 当前宏任务执行完毕,开始执行微任务队列(
() => console.log(3)),输出3 - 此时微任务队列为空
- 此时宏任务队列也为空,结束
1 | |
- 脚本开始执行,即当前宏任务
console.log(1)是同步任务,立刻输出1setTimeout()的回调是宏任务,此时宏任务队列为() => console.log(2)- 几个
.then()的回调都是微任务,此时微任务队列为() => console.log(3),() => setTimeout(() => console.log(4)),Promise.resolve().then(() => console.log(5)) setTimeout()的回调是宏任务,此时宏任务队列为() => console.log(2),() => console.log(6)console.log(7)是同步任务,立刻输出7- 当前宏任务执行完毕,开始执行微任务队列(
() => console.log(3),() => setTimeout(() => console.log(4)),() => Promise.resolve().then(() => console.log(5))) console.log(3)立刻输出3setTimeout()又注册了新的宏任务,此时宏任务队列为() => console.log(2),() => console.log(6),() => console.log(4)- 在执行第三个微任务时,
.then()又把微任务添加到了队列,因此此时微任务队列还是不为空,为() => console.log(5) console.log(5)立刻输出5- 此时微任务队列为空,开始执行宏任务
() => console.log(2),立刻输出2 - 此时微任务队列依旧为空,开始执行宏任务
() => console.log(6),立刻输出6 - 此时微任务队列依旧为空,开始执行宏任务
() => console.log(4),立刻输出4 - 此时宏任务队列为空,结束
参考资料
JavaScript的宏任务和微任务
https://tomzhu.site/2026/06/28/JavaScript的宏任务和微任务/