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");
我希望看到以下输出:
你好
计时器 1
立即数 1
I/O
计时器 2
立即数 2
计时器 3
但我得到以下输出:
你好
计时器 1
立即数 1
I/O
立即数 2
计时器 2
计时器 3
请你解释一下这些行是如何一步步执行的。
此输出的原因是 javascript 的异步性质。
- 您将前 2 个输出设置为某种超时,执行时间为 0,这使它们仍然等待滴答。
- 接下来您要读取文件,这需要一段时间才能完成,因此会延迟回调中函数的执行
- 回调中的第一个 console.log 会在回调执行后立即触发,回调中的其余部分遵循代码的第一部分
- 最后,底部的 console.log 首先执行,因为它没有延迟,不需要等到下一个订单号。
作为一些额外的帮助,请观看此视频。
主持人就事件循环发表了精彩的演讲。我认为这是一个很好的资源。
虽然这是专门针对浏览器的,但在 Node 中共享许多方面。
首先,我要提一下,如果您真的希望异步操作 A 以与异步操作 B 相关的特定顺序进行处理,您可能应该编写代码以保证不依赖于异步操作 B 的细节究竟是什么首先到达 运行。但是,也就是说,我 运行 遇到这样的问题,即一种类型的异步操作可以“霸占”事件循环并使其他类型的事件挨饿,了解内部真正发生的事情可能很有用 if/when发生了。
归根结底,您的问题实际上是关于为什么 Immediate2
在 Timer2
之前从 I/O 回调中安排记录,但不是从顶级代码调用时?因此不一致。
这与事件循环在其循环中的位置有关,通过在 setTimeout()
和 setImmediate()
被调用时(当它们被调度时)它正在做的各种检查。这里稍微解释一下:https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/#setimmediate-vs-settimeout.
如果你看一下这个事件循环的简化图(来自上面的文章):
您可以看到事件循环有许多不同的部分。 setTimeout()
由图表顶部的“计时器”块提供服务。 setImmediate()
在图表底部附近的“检查”块中提供。文件 I/O 在中间的“轮询”块中提供。
因此,如果您在文件 I/O 回调中同时安排 setImmediate(fn1)
和 setTimeout(fn2, 0)
(Intermediate2 和 Timer2 就是这种情况),那么事件循环处理这两个调度的时候恰好处于poll阶段。因此,事件循环的下一阶段是“检查”阶段,setImmediate(fn1)
得到处理。然后,在“检查”阶段和“关闭回调”阶段之后,它会循环回到“计时器”阶段,您会得到 setTimeout(fn2,0)
.
另一方面,如果您从事件循环的不同阶段从 运行 的代码中调用相同的两个 setImmediate()
和 setTimeout()
,则计时器可能在 setImmediate()
之前首先处理 - 这将取决于代码在事件循环周期中执行的确切位置。
事件循环的这种结构就是为什么有人将 setImmediate()
描述为“它 运行 就在 I/O 之后”,因为它位于循环中,在“轮询”阶段。如果您正在 I/O 回调中处理某些文件 I/O 并且希望在堆栈展开后立即 运行 某些内容,您可以使用 setImmediate()
来完成那。它总是 运行 在当前 I/O 回调完成之后,但在计时器之前。
注意:此简化描述中遗漏了具有特殊待遇的承诺。 Promise 被认为是微任务,它们有自己的队列。他们更频繁地到达 运行。从节点 v11 开始,它们在事件循环的每个阶段都达到 运行。因此,如果您有三个准备好 运行 的挂起计时器,并且您进入事件循环的计时器阶段并为第一个挂起计时器调用回调,并且在该计时器回调中,您解决了一个承诺,然后作为一旦那个定时器回调 returns 回到系统,它就会服务于那个已解决的承诺。因此,微任务(例如 promises 和 process.nextTick()
)在事件循环中的每个操作之间得到服务(如果等待 运行),不仅在事件循环的阶段之间,甚至在事件循环中的未决事件之间同相。您可以在此处阅读有关这些细节和节点 v11 更改的更多信息:New Changes to the Timers and Microtasks in Node v11.0.0 and above.
我相信这样做是为了提高与 promise 相关的代码的性能,因为 promises 已成为异步操作的 nodejs 体系结构的核心部分,并且在该领域也有一些与标准相关的工作来实现这一点在不同的 JS 环境中保持一致。
这是另一个涵盖其中部分内容的参考资料:
Nodejs Event Loop - interaction with top-level code
我正在学习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");
我希望看到以下输出:
你好
计时器 1
立即数 1
I/O
计时器 2
立即数 2
计时器 3
但我得到以下输出:
你好
计时器 1
立即数 1
I/O
立即数 2
计时器 2
计时器 3
请你解释一下这些行是如何一步步执行的。
此输出的原因是 javascript 的异步性质。
- 您将前 2 个输出设置为某种超时,执行时间为 0,这使它们仍然等待滴答。
- 接下来您要读取文件,这需要一段时间才能完成,因此会延迟回调中函数的执行
- 回调中的第一个 console.log 会在回调执行后立即触发,回调中的其余部分遵循代码的第一部分
- 最后,底部的 console.log 首先执行,因为它没有延迟,不需要等到下一个订单号。
作为一些额外的帮助,请观看此视频。
主持人就事件循环发表了精彩的演讲。我认为这是一个很好的资源。
虽然这是专门针对浏览器的,但在 Node 中共享许多方面。
首先,我要提一下,如果您真的希望异步操作 A 以与异步操作 B 相关的特定顺序进行处理,您可能应该编写代码以保证不依赖于异步操作 B 的细节究竟是什么首先到达 运行。但是,也就是说,我 运行 遇到这样的问题,即一种类型的异步操作可以“霸占”事件循环并使其他类型的事件挨饿,了解内部真正发生的事情可能很有用 if/when发生了。
归根结底,您的问题实际上是关于为什么 Immediate2
在 Timer2
之前从 I/O 回调中安排记录,但不是从顶级代码调用时?因此不一致。
这与事件循环在其循环中的位置有关,通过在 setTimeout()
和 setImmediate()
被调用时(当它们被调度时)它正在做的各种检查。这里稍微解释一下:https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/#setimmediate-vs-settimeout.
如果你看一下这个事件循环的简化图(来自上面的文章):
您可以看到事件循环有许多不同的部分。 setTimeout()
由图表顶部的“计时器”块提供服务。 setImmediate()
在图表底部附近的“检查”块中提供。文件 I/O 在中间的“轮询”块中提供。
因此,如果您在文件 I/O 回调中同时安排 setImmediate(fn1)
和 setTimeout(fn2, 0)
(Intermediate2 和 Timer2 就是这种情况),那么事件循环处理这两个调度的时候恰好处于poll阶段。因此,事件循环的下一阶段是“检查”阶段,setImmediate(fn1)
得到处理。然后,在“检查”阶段和“关闭回调”阶段之后,它会循环回到“计时器”阶段,您会得到 setTimeout(fn2,0)
.
另一方面,如果您从事件循环的不同阶段从 运行 的代码中调用相同的两个 setImmediate()
和 setTimeout()
,则计时器可能在 setImmediate()
之前首先处理 - 这将取决于代码在事件循环周期中执行的确切位置。
事件循环的这种结构就是为什么有人将 setImmediate()
描述为“它 运行 就在 I/O 之后”,因为它位于循环中,在“轮询”阶段。如果您正在 I/O 回调中处理某些文件 I/O 并且希望在堆栈展开后立即 运行 某些内容,您可以使用 setImmediate()
来完成那。它总是 运行 在当前 I/O 回调完成之后,但在计时器之前。
注意:此简化描述中遗漏了具有特殊待遇的承诺。 Promise 被认为是微任务,它们有自己的队列。他们更频繁地到达 运行。从节点 v11 开始,它们在事件循环的每个阶段都达到 运行。因此,如果您有三个准备好 运行 的挂起计时器,并且您进入事件循环的计时器阶段并为第一个挂起计时器调用回调,并且在该计时器回调中,您解决了一个承诺,然后作为一旦那个定时器回调 returns 回到系统,它就会服务于那个已解决的承诺。因此,微任务(例如 promises 和 process.nextTick()
)在事件循环中的每个操作之间得到服务(如果等待 运行),不仅在事件循环的阶段之间,甚至在事件循环中的未决事件之间同相。您可以在此处阅读有关这些细节和节点 v11 更改的更多信息:New Changes to the Timers and Microtasks in Node v11.0.0 and above.
我相信这样做是为了提高与 promise 相关的代码的性能,因为 promises 已成为异步操作的 nodejs 体系结构的核心部分,并且在该领域也有一些与标准相关的工作来实现这一点在不同的 JS 环境中保持一致。
这是另一个涵盖其中部分内容的参考资料:
Nodejs Event Loop - interaction with top-level code