繁体   English   中英

Node.js 事件循环机制

[英]Node.js Event-Loop Mechanism

我正在学习 Node.js 中的 Event-Loop 的机制,并且我正在做一些练习,但有一些困惑,如下所述。

const fs = require("fs");

setTimeout(() => console.log("Timer 1"), 0);
setImmediate(() => console.log("Immediate 1"));

fs.readFile("test-file-with-1-million-lines.txt", () => {
  console.log("I/O");

  setTimeout(() => console.log("Timer 2"), 0);
  setTimeout(() => console.log("Timer 3"), 3000);
  setImmediate(() => console.log("Immediate 2"));
});

console.log("Hello");

我预计会看到以下 output:

你好
定时器 1
立即 1
输入输出
定时器 2
立即 2
定时器 3

但我得到以下 output:

你好
定时器 1
立即 1
输入输出
立即 2
定时器 2
定时器 3

请您为我澄清一下这些行是如何逐步执行的。

这个 output 的原因是 javascript 的异步特性。

  • 您将前 2 个输出设置为超时,执行时间为 0,这使它们仍然等待一个滴答声。
  • 接下来,您需要读取文件,这需要一段时间才能完成,因此会延迟回调中函数的执行
  • 回调中的第一个 console.log 会在回调执行后立即触发,并且回调中的 rest 遵循代码的第一部分
  • 最后,您在底部有 console.log,它首先被执行,因为它没有延迟,并且不需要等到下一个滴答声。

作为一些附加帮助,请查看此视频。

https://youtu.be/cCOL7MC4Pl0

主持人就事件循环进行了精彩的演讲。 我认为这是一个很好的资源。

虽然这尤其适用于浏览器,但 Node.js 共享了许多方面。

首先,我应该提一下,如果您真的希望异步操作 A 以相对于异步操作 B 的特定顺序进行处理,您可能应该编写代码,这样它可以保证在不依赖确切运行的细节的情况下第一的。 但是,也就是说,我遇到了一种类型的异步操作可以“占用”事件循环并使其他类型的事件饿死的问题,如果/何时发生这种情况,了解内部真正发生的事情可能很有用。

分解到它的核心,你的问题实际上是关于为什么Immediate2在从 I/O 回调中调度时在Timer2之前记录,而不是从顶级代码调用时? 因此是不一致的。

这与在调用setTimeout()setImmediate()时(在调度它们时)通过各种检查事件循环在其循环中所处的位置有关。 这里有些解释: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/#setimmediate-vs-settimeout

如果你看一下这个事件循环的简化图(来自上面的文章):

在此处输入图像描述

您可以看到事件循环有许多不同的部分。 setTimeout()由图表顶部的“计时器”块提供服务。 setImmediate()在图表底部附近的“检查”块中提供。 文件 I/O 在中间的“poll”块中提供。

因此,如果您从文件 I/O 回调(Intermediate2 和 Timer2 的情况)中同时安排setImmediate(fn1)setTimeout(fn2, 0) ,则事件循环处理恰好处于轮询阶段当这两个被安排时。 因此,事件循环的下一个阶段是“检查”阶段,并且setImmediate(fn1)得到处理。 然后,在“检查”阶段和“关闭回调”阶段之后,它会循环回到“计时器”阶段,你会得到setTimeout(fn2,0)

另一方面,如果您从从事件循环的不同阶段运行的代码中调用这两个相同的setImmediate()setTimeout() ,那么计时器可能会在setImmediate()之前首先得到处理 - 这将完全取决于在事件循环周期中执行该代码的位置。

事件循环的这种结构是为什么有些人将setImmediate()描述为“它在 I/O 之后立即运行”,因为它位于循环中,以便在“轮询”阶段之后立即处理。 如果您正在 I/O 回调中处理某些文件 I/O,并且希望在堆栈展开后立即运行某些内容,则可以使用setImmediate()来完成此操作。 它总是在当前 I/O 回调完成之后,但在计时器之前运行。

注意:这个简化的描述中缺少有自己特殊处理的承诺。 Promise 被认为是微任务,它们有自己的队列。 他们可以更频繁地运行。 从节点 v11 开始,它们可以在事件循环的每个阶段运行。 因此,如果您有三个准备运行的挂起计时器,并且您进入事件循环的计时器阶段并调用第一个挂起计时器的回调,并且在该计时器回调中,您解决了 promise,然后只要该计时器回调返回到系统,然后它将为已解析的 promise 提供服务。 因此,微任务(例如 promises 和process.nextTick() )在事件循环中的每个操作之间(如果等待运行)得到服务,不仅在事件循环的阶段之间,甚至在同一阶段的未决事件之间。 您可以在此处阅读有关这些细节和节点 v11 更改的更多信息: Node v11.0.0 及更高版本中计时器和微任务的新更改

我相信这样做是为了提高与 Promise 相关的代码的性能,因为 Promise 已成为 Node.js 架构中异步操作的核心部分,并且在该领域也有一些与标准相关的工作,以使其在不同的 JS 环境中保持一致.

这是涵盖部分内容的另一个参考:

Nodejs 事件循环 - 与顶级代码的交互

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM