Skip to content

Promise 与事件循环

Promise 不只是“更好写的异步语法”,它和事件循环直接相关。真正理解 Promise.then()await 的执行时机,需要把它放回事件循环模型里看。

先说结论

  • Promise 的回调不会同步立即执行。
  • then / catch / finally 的回调会进入微任务队列。
  • 当前同步代码执行完之后,浏览器会先清空微任务,再进入下一轮任务调度。

一个最基础的例子

js
console.log(1)

Promise.resolve().then(() => {
  console.log(2)
})

console.log(3)

输出顺序是:

js
1
3
2

原因是:

  1. console.log(1) 同步执行。
  2. Promise.then 回调进入微任务队列。
  3. console.log(3) 同步执行。
  4. 同步代码结束后,清空微任务,输出 2

为什么是微任务

浏览器事件循环中,常见可以分为两类理解:

  • 任务:例如 setTimeout、DOM 事件、脚本执行
  • 微任务:例如 Promise.thenqueueMicrotaskMutationObserver

微任务优先级更高。每轮任务执行结束后,都会先把当前产生的微任务清空。

对比 setTimeout

js
console.log(1)

setTimeout(() => {
  console.log(2)
}, 0)

Promise.resolve().then(() => {
  console.log(3)
})

console.log(4)

输出顺序:

js
1
4
3
2

原因:

  • setTimeout 回调进入后续任务队列
  • Promise.then 回调进入微任务队列
  • 当前同步代码结束后,先执行微任务,再等下一轮任务执行 setTimeout

Promise 链为什么是按顺序执行

js
Promise.resolve()
  .then(() => {
    console.log(1)
  })
  .then(() => {
    console.log(2)
  })

第一个 then 执行完后,第二个 then 对应的回调会继续进入微任务队列,因此看起来就像“连续异步但顺序稳定”。

await 为什么也像微任务

async/await 本质上是 Promise 的语法糖,所以 await 后续代码的恢复执行,也会走微任务调度。

js
async function run() {
  console.log(1)
  await Promise.resolve()
  console.log(2)
}

console.log(0)
run()
console.log(3)

输出顺序:

js
0
1
3
2

一个常见面试题

js
console.log('start')

setTimeout(() => {
  console.log('timeout')
}, 0)

Promise.resolve()
  .then(() => {
    console.log('promise1')
  })
  .then(() => {
    console.log('promise2')
  })

console.log('end')

输出:

js
start
end
promise1
promise2
timeout

理解顺序

遇到这类题目,可以按这个顺序看:

  1. 先执行所有同步代码。
  2. Promise.then / await 后续逻辑放进微任务。
  3. setTimeout 之类放进后续任务。
  4. 同步结束后先清空微任务。
  5. 再进入下一轮任务执行。

和浏览器事件循环页的关系

  • /web/core/web-api/advance/event-loop 更偏浏览器运行机制
  • 当前这页更偏 JavaScript 语言层如何借助微任务调度 Promise

两页合起来看,会更容易把异步执行顺序真正串起来。

基于 MIT 许可发布