coverPiccoverPic

Node.js 的事件循环机制简介

前言

Javascript 在浏览器中的事件循环(Event Loop)机制,其是根据 HTML5 定义的规范来实现,而 Node 的事件循环基于异步 IO 库 libuv 实现。Node 启动时将会初始化并执行事件循环。

事件循环

Node 事件循环有以下 6 个阶段:

graph LR
A(timers)-->B("pending callbacks")-->C("idle, prepare")-->D("poll")-->E("check")-->F("close callbacks")-->A

timers 阶段,Node 处理超时的setTimeoutsetInterval

pending callbacks 阶段执行某些系统操作的回调,例如 TCP 连接错误。

idle, prepare 阶段仅在 Node 内部使用。

poll 轮询阶段计算应该阻塞和轮询 I/O 的时间,然后处理轮询队列中的事件。当事件循环进入 poll 阶段并且没有计时器时,

  1. 如果轮询队列不为空,则事件循环将迭代其回调队列,同步执行它们,直到队列耗尽或达到系统相关的时间限制。

  2. 如果轮询队列为空,如果有setImmediate调度,结束本阶段,并且进入 check 阶段阶段执行调度的代码。否则,事件循环将等待回调被添加到队列中,然后立即执行。

如果轮询队列为空,将检查是否有计时器达到阈值。有则返回到 timer 阶段以执行计时器回调。

check 阶段执行setImmediate的回调。该阶段意味着 poll 阶段的结束。

close callbacks 执行关闭的回调,例如socket.on('close', ...)

总结一下:

graph LR
A(timers)-->B("pending callbacks")-->C("idle, prepare")-->D("poll")-->|达到限制|E("check")-->F("close callbacks")-->A
D-->|"轮询队列为空 & 没有计时器 & setImmediate"|E
D-->|"轮询队列为空 & 计时器"|A

微任务

上面的所说的回调,其实是宏任务(Macrotask)。Node 的微任务有 3 种:Promise.thenprocess.nextTickqueueMicrotask。Node 11 之后,在每一个阶段的每一个宏任务执行前(之前是执行每一阶段前),Node 执行并清空微任务队列,也包括执行微任务时产生的微任务。

Promise.thenprocess.nextTick的任务属于不同的微任务队列。前者由 V8 引擎管理,后者由 Node 自身管理。Node 会先执行process.nextTick的队列,再执行Promise.then的。queueMicrotask这个微任务和Promise.then使用同一个微任务队列。

此外,Node 会先执行完当前微队列的任务,举个栗子:

ts
  1. import process from "process";
  2. Promise.resolve().then(() => {
  3. console.log(2);
  4. process.nextTick(() => console.log(5))
  5. Promise.resolve().then(() => {
  6. console.log(3)
  7. Promise.resolve().then(() => {
  8. console.log(4)
  9. })
  10. })
  11. })
  12. process.nextTick(() => console.log(0))
  13. queueMicrotask(() => console.log(1))

这段代码结果是 0 1 2 3 4 5。process.nextTick优先,则先打印 0。另外,可以发现process.nextTick的 5 排在Promise.resolve().then的 3 4 后面,这是因为 Node 执行某个任务队列时,会先执行完该队列任务。

0 条评论未登录用户
Ctrl or + Enter 评论
🌸 Run