Node.js 动画:事件循环阶段
在上一篇文章《Node.js 动画:事件循环》中,您探讨了 Libuv 和事件循环如何在 Node.js 的单线程环境中实现异步任务处理。我们将事件循环简化为一种将回调从单个 事件队列推 送到空调用栈的机制。实际上,事件循环由多个阶段组成,每个阶段负责特定的异步任务。
事件循环阶段
阶段是一个先进先出(FIFO)的回调队列,用于存放待执行的回调。当事件循环进入某个阶段时,它会执行回调,直到队列中的回调被清空或达到最大执行次数,然后进入下一阶段。
事件循环包含 6 个 阶段,它们按以下顺序运行:
- 定时器:执行由 setTimeout() 和 setInterval() 安排的回调函数。
- 待处理的回调:执行延迟到下一次循环迭代的 I/O 回调。
- 空闲准备:仅供内部使用。
- 轮询:检索新的 I/O 事件并执行与 I/O 相关的回调函数
- 检查:执行由 setImmediate() 安排的回调。
- 关闭回调:一些关闭回调,例如 socket.on('close', …)。
事件循环并非持续循环。如果没有活动的 I/O 处理程序,且事件循环不需要处理任何回调,Node.js 程序会自动退出。另一方面,当您创建 Web 服务器且轮询阶段为空时,事件循环会在轮询阶段停止以捕获传入的请求。
定时器和检查阶段
你已经准备好运用新获得的知识。然而,当你先运行一个函数,setTimeout再运行另一个函数setImmediate,或者反之亦然时,你可能会感到失望,因为结果并非确定性的。
setTimeout至少经过设定的毫秒数后,会安排一个回调函数 。CPU 可能正忙,Libuv 无法在事件循环处理完计时器阶段之前将任务标记为已完成。因此,即使计时器是第一阶段,setImmediate 回调函数也可能提前运行。
在这个例子中,CPU 并不忙,setTimeout回调函数会立即被添加到其专用队列中setImmedate。
在第二个示例中,事件循环开始处理计时器阶段,但计时器尚未到期,因此提前运行 setImmediate 回调。
探索民意调查阶段
您现在可能想知道我如何测试各个阶段的处理顺序。以下代码片段将帮助您验证各个阶段的顺序。
Node.js 程序启动后,事件循环会处理所有队列,并在轮询阶段阻塞等待传入的请求。当有人向服务器发出 GET HTTP 请求时,事件循环会执行以下步骤:
- 事件循环将请求处理程序推入调用堆栈。
- 在检查
setImmediate阶段添加了回调 函数 。 - 回调操作在定时器
setTimeout阶段进行安排 。 - 事件循环进入检查阶段。
setImmediate回调函数从队列中弹出并压入调用栈以进行执行。 - 事件循环不会将任何回调从空队列中移出。
- 回调函数
setTimeout从定时器队列中弹出,并被压入调用栈进行处理。
代码可在以下位置获取:
Animated Node.js-02-event-loop-phases 仓库
结论
现在,您已经了解了事件循环及其各个阶段。您也能区分 `poll`setTimeout和setImmediate`await`,并预料到按顺序运行它们时会出现不确定的行为。此外,您还知道事件循环会在 `poll` 阶段阻塞,等待 Node.js Web 服务器上的新 I/O 请求。
但是Node.js中是如何处理Promise的呢?
您将在后续文章中更详细地了解它。
资源
如果您喜欢这篇文章,请在 Twitter 上关注我们:@fabrizio.lallo和@AndrewHu368
文章来源:https://dev.to/nodedoctors/animated-nodejs-event-loop-phases-1mcp

