Skip to content

Event Loop 的执行顺序是什么? ​

当前调用栈清空之后,先把微任务队列全部跑完,再取一个宏任务执行,然后再次清空微任务。浏览器绘制发生在这几轮之间,不是每执行一个任务就画一帧。

原理 ​

在浏览器里可以先按这两类记:

  • 宏任务:整段脚本、setTimeout、setInterval、I/O、用户事件、postMessage
  • 微任务:Promise.then / catch / finally、queueMicrotask、MutationObserver
js
console.log("1")
setTimeout(() => console.log("2"), 0)
Promise.resolve().then(() => console.log("3"))
console.log("4")
// 1 4 3 2

同步代码先打印 1 和 4。then 是微任务,排在 setTimeout 前面,所以 3 在 2 前面。

async/await 建立在 Promise 上。await 之前的代码同步执行,await 之后的代码相当于放进 then:

js
async function run() {
  console.log("a")
  await null
  console.log("b")
}
run()
console.log("c")
// a c b

setTimeout(fn, 0) 也要等当前栈和微任务结束。嵌套层数变多时,浏览器还会把定时器延迟夹到至少 4ms,所以它不是「立刻执行」。

requestAnimationFrame 在下一次绘制前调用,既不是宏任务也不是微任务。用它做动画,不要用 setTimeout 去卡 16ms。

Node.js 的轮询阶段和浏览器不一样。没限定环境时先讲浏览器。如果问到 Node,再补:process.nextTick 比 Promise 微任务更早,setImmediate 在 check 阶段。

追问 ​

微任务一直排队会怎样?

下一轮宏任务和渲染都会被推迟,页面点了没反应。不要在微任务里无限制地 queueMicrotask 或链式 then。

先 setTimeout 再 Promise,和反过来,结果一样吗?

谁先注册不改变类别。只要都是在同一次同步代码里注册的,微任务仍然全部先于下一个宏任务。

易错点 ​

  • async 函数在第一个 await 之前都是同步的。
  • then 的回调至少要晚一个微任务,即使 Promise 已经完成。
  • 把 requestAnimationFrame 说成宏任务或微任务都不准确。

相关 ​