发布于 2026-01-05 3 阅读
0

Node.js 动画:事件循环阶段

Node.js 动画:事件循环阶段

在上一篇文章《Node.js 动画:事件循环》中,您探讨了 Libuv 和事件循环如何在 Node.js 的单线程环境中实现异步任务处理。我们将事件循环简化为一种将回调从单个 事件队列推 送到空调用栈的机制。实际上,事件循环由多个阶段组成,每个阶段负责特定的异步任务。

事件循环阶段

事件循环阶段

阶段是一个先进先出(FIFO)的回调队列,用于存放待执行的回调。当事件循环进入某个阶段时,它会执行回调,直到队列中的回调被清空或达到最大执行次数,然后进入下一阶段。

事件循环包含 6 个 阶段,它们按以下顺序运行:

  • 定时器:执行由 setTimeout() 和 setInterval() 安排的回调函数。
  • 待处理的回调:执行延迟到下一次循环迭代的 I/O 回调。
  • 空闲准备:仅供内部使用。
  • 轮询:检索新的 I/O 事件并执行与 I/O 相关的回调函数
  • 检查:执行由 setImmediate() 安排的回调。
  • 关闭回调:一些关闭回调,例如 socket.on('close', …)。

event_loop_phases_handled

事件循环并非持续循环。如果没有活动的 I/O 处理程序,且事件循环不需要处理任何回调,Node.js 程序会自动退出。另一方面,当您创建 Web 服务器且轮询阶段为空时,事件循环会在轮询阶段停止以捕获传入的请求。

定时器和检查阶段

你已经准备好运用新获得的知识。然而,当你先运行一个函数,setTimeout再运行另一个函数setImmediate,或者反之亦然时,你可能会感到失望,因为结果并非确定性的。

setTimeout至少经过设定的毫秒数后,会安排一个回调函数  。CPU 可能正忙,Libuv 无法在事件循环处理完计时器阶段之前将任务标记为已完成。因此,即使计时器是第一阶段,setImmediate 回调函数也可能提前运行。

在这个例子中,CPU 并不忙,setTimeout回调函数会立即被添加到其专用队列中setImmedate

在第二个示例中,事件循环开始处理计时器阶段,但计时器尚未到期,因此提前运行 setImmediate 回调。

探索民意调查阶段

您现在可能想知道我如何测试各个阶段的处理顺序。以下代码片段将帮助您验证各个阶段的顺序。

轮询阶段

Node.js 程序启动后,事件循环会处理所有队列,并在轮询阶段阻塞等待传入的请求。当有人向服务器发出 GET HTTP 请求时,事件循环会执行以下步骤:

  1. 事件循环将请求处理程序推入调用堆栈。
  2. 在检查setImmediate阶段添加了回调 函数 。
  3. 回调操作在定时器setTimeout阶段进行安排  。
  4. 事件循环进入检查阶段。setImmediate回调函数从队列中弹出并压入调用栈以进行执行。
  5. 事件循环不会将任何回调从空队列中移出。
  6. 回调函数setTimeout从定时器队列中弹出,并被压入调用栈进行处理。

代码可在以下位置获取:

Animated Node.js-02-event-loop-phases 仓库

结论

现在,您已经了解了事件循环及其各个阶段。您也能区分 `poll`setTimeoutsetImmediate`await`,并预料到按顺序运行它们时会出现不确定的行为。此外,您还知道事件循环会在 `poll` 阶段阻塞,等待 Node.js Web 服务器上的新 I/O 请求。

但是Node.js中是如何处理Promise的呢?

您将在后续文章中更详细地了解它。

资源

如果您喜欢这篇文章,请在 Twitter 上关注我们:@fabrizio.lallo@AndrewHu368

文章来源:https://dev.to/nodedoctors/animated-nodejs-event-loop-phases-1mcp