@@ -9,7 +9,7 @@ layout: docs.hbs
99
1010事件循环是 Node.js 处理非阻塞 I/O 操作的机制——尽管 JavaScript 是单线程处理的——当有可能的时候,它们会把操作转移到系统内核中去。
1111
12- 既然目前大多数内核都是多线程的,它们可在后台处理多种操作 。当其中的一个操作完成的时候,内核通知 Node.js 将适合的回调函数添加到 * 轮询* 队列中等待时机执行。我们在本文后面会进行详细介绍。
12+ 因为目前大多数内核都是多线程的,所以它们可以在后台处理多种操作 。当其中的一个操作完成的时候,内核通知 Node.js 将适合的回调函数添加到 * 轮询* 队列中等待时机执行。我们在本文后面会进行详细介绍。
1313
1414## 事件循环机制解析
1515
@@ -40,9 +40,9 @@ layout: docs.hbs
4040
4141* 注意:每个框被称为事件循环机制的一个阶段。*
4242
43- 每个阶段都有一个 FIFO 队列来执行回调。虽然每个阶段都是特殊的,但通常情况下,当事件循环进入给定的阶段时,它将执行特定于该阶段的任何操作,然后执行该阶段队列中的回调,直到队列用尽或最大回调数已执行 。当该队列已用尽或达到回调限制,事件循环将移动到下一阶段,等等 。
43+ 每个阶段都有一个 FIFO 队列来执行回调。虽然每个阶段都是特殊的,但通常情况下,当事件循环进入给定的阶段时,它将执行特定于该阶段的任何操作,然后执行该阶段队列中的回调,直到队列用尽或已经执行到最大的回调数 。当该队列已用尽或达到回调限制,事件循环将移动到下一阶段,以此类推 。
4444
45- 由于这些操作中的任何一个都可能调度 _ 更多的_ 操作和由内核排列在 ** 轮询** 阶段被处理的新事件, 且在处理轮询中的事件时 ,轮询事件可以排队。因此,长时间运行的回调可以允许轮询阶段运行长于计时器的阈值时间 。有关详细信息,请参阅 [ ** 计时器** ] ( #timers ) 和 [ ** 轮询** ] ( #poll ) 部分。
45+ 由于这些操作中的任何一个都可能调度 _ 更多的_ 操作并且在 ** 轮询(poll) ** 阶段被处理的新事件会被内核排列, 并且在处理轮询中的事件时 ,轮询事件可以排队。因此,长时间运行的回调可以允许轮询阶段运行长于计时器的 ** 阈值(threshold) ** 。有关详细信息,请参阅 [ ** 计时器** ] ( #timers ) 和 [ ** 轮询** ] ( #poll ) 部分。
4646
4747_ ** 注意:** 在 Windows 和 Unix/Linux 实现之间存在细微的差异,但这对演示来说并不重要。最重要的部分在这里。实际上有七或八个步骤,但我们关心的是 Node.js 实际上使用以上的某些步骤。_
4848
@@ -61,12 +61,12 @@ _**注意:** 在 Windows 和 Unix/Linux 实现之间存在细微的差异,
6161
6262### <!-- timers--> 定时器
6363
64- 计时器指定 _ 可以执行所提供回调 _ 的 ** 阈值** ,而不是用户希望其执行的确切时间。在指定的一段时间间隔后 ,
64+ 计时器可以 _ 在回调后面 _ 指定 ** 阈值** ,而不是用户希望回调执行的确切时间。因为在经过指定的一段时间间隔后 ,
6565计时器回调将被尽可能早地运行。但是,操作系统调度或其它正在运行的回调可能会延迟它们。
6666
6767_ ** 注意** :[ ** 轮询** 阶段] ( #poll ) 控制何时定时器执行。_
6868
69- 例如,假设您调度了一个在 100 毫秒后超时的定时器,然后您的脚本开始异步读取会耗费 95 毫秒的文件 :
69+ 例如,您调度了一个在 100 毫秒后执行回调的定时器,并且您的脚本开始异步读取文件,这会耗费 95 毫秒 :
7070
7171``` js
7272const fs = require (' fs' );
@@ -95,10 +95,10 @@ someAsyncOperation(() => {
9595});
9696```
9797
98- 当事件循环进入 ** 轮询** 阶段时,它有一个空队列(此时 ` fs.readFile() ` 尚未完成),因此它将等待剩下的毫秒数,直到达到最快的一个计时器阈值为止。当它等待 95 毫秒过后时 ,` fs.readFile() ` 完成读取文件,它的那个需要 10 毫秒才能完成的回调,将被添加到 ** 轮询** 队列中并执行。当回调完成时,队列中不再有回调,因此事件循环机制将查看最快到达阈值的计时器 ,然后将回到 ** 计时器** 阶段,以执行定时器的回调。在本示例中,您将看到调度计时器到它的回调被执行之间的总延迟将为 105 毫秒。
98+ 当事件循环进入 ** 轮询(poll) ** 阶段时,它有一个空队列(此时 ` fs.readFile() ` 尚未完成),因此它将等待剩下的毫秒数,直到达到最快的一个计时器阈值为止。当它等待 95 毫秒过后 ,` fs.readFile() ` 完成读取文件,它的那个需要 10 毫秒才能完成的回调将被添加到 ** 轮询** 队列中并执行。当回调完成时,队列中不再有回调,此时事件循环机制将发现计时器最快的阈值(100ms)的已经达到 ,然后将回到 ** 计时器** 阶段,以执行定时器的回调。在本示例中,您将看到调度计时器到它的回调被执行之间的总延迟将为 105 毫秒。
9999
100- 注意:为了防止 ** 轮询** 阶段饿死事件循环 ,[ libuv] [ ] (实现 Node.js
101- 事件循环和平台的所有异步行为的 C 函数库), 在停止轮询以获得更多事件之前,还有一个硬性最大值 (依赖于系统)。
100+ 注意:为了防止 ** 轮询** 阶段事件循环陷入吃不饱的状态 ,[ libuv] [ ] (实现 Node.js
101+ 事件循环和平台的所有异步行为的 C 函数库)在停止轮询以获得更多事件之前,还有一个硬性的最大值 (依赖于系统)。
102102
103103### 挂起的回调函数
104104
@@ -124,11 +124,11 @@ someAsyncOperation(() => {
124124
125125### 检查阶段
126126
127- 此阶段允许人员在轮询阶段完成后立即执行回调 。如果轮询阶段变为空闲状态,并且脚本使用 ` setImmediate() ` 后被排列在队列中,则事件循环可能继续到 ** 检查** 阶段而不是等待。
127+ 此阶段允许人员在 ** 轮询 ** 阶段完成后立即执行回调 。如果轮询阶段变为空闲状态,并且脚本使用 ` setImmediate() ` 后被排列在队列中,则事件循环可能继续到 ** 检查** 阶段而不是等待。
128128
129129` setImmediate() ` 实际上是一个在事件循环的单独阶段运行的特殊计时器。它使用一个 libuv API 来安排回调在 ** 轮询** 阶段完成后执行。
130130
131- 通常,在执行代码时,事件循环最终会命中轮询阶段,在那等待传入连接 、请求等。但是,如果回调已使用 ` setImmediate() ` 调度过,并且轮询阶段变为空闲状态,则它将结束此阶段,并继续到检查阶段而不是继续等待轮询事件。
131+ 通常,在执行代码时,事件循环最终会进入轮询阶段,在该阶段它将等待传入连接 、请求等。但是,如果回调已使用 ` setImmediate() ` 调度过,并且轮询阶段变为空闲状态,则它将结束此阶段,并继续到检查阶段而不是继续等待轮询事件。
132132
133133### 关闭的回调函数
134134
@@ -141,7 +141,7 @@ someAsyncOperation(() => {
141141* ` setImmediate() ` 设计为一旦在当前 ** 轮询** 阶段完成, 就执行脚本。
142142* ` setTimeout() ` 在最小阈值(ms 单位)过后运行脚本。
143143
144- 执行计时器的顺序将根据调用它们的上下文而异。如果二者都从主模块内调用,则计时器将受进程性能的约束 (这可能会受到计算机上其他正在运行应用程序的影响)。
144+ 执行计时器的顺序将根据调用它们的上下文而异。如果二者都从主模块内调用,则时序将受进程性能的约束 (这可能会受到计算机上其他正在运行应用程序的影响)。
145145
146146例如,如果运行以下不在 I/O 周期(即主模块)内的脚本,则执行两个计时器的顺序是非确定性的,因为它受进程性能的约束:
147147
@@ -192,19 +192,19 @@ immediate
192192timeout
193193```
194194
195- 使用 ` setImmediate() ` 相对于` setTimeout() ` 的主要优势是,如果` setImmediate() ` 是在 I/O 周期内被调度的,那它将会在其中任何的定时器之前执行,跟这里存在多少个定时器无关
195+ 使用 ` setImmediate() ` 相对于` setTimeout() ` 的主要优势是,如果` setImmediate() ` 是在 I/O 周期内被调度的,那它将会在其中任何的定时器之前执行,跟这里存在多少个定时器无关。
196196
197197## ` process.nextTick() `
198198
199199### 理解 ` process.nextTick() `
200200
201- 您可能已经注意到 ` process.nextTick() ` 在图示中没有显示,即使它是异步 API 的一部分。这是因为 ` process.nextTick() ` 从技术上讲不是事件循环的一部分。相反,它都将在当前操作完成后处理 ` nextTickQueue ` , 而不管事件循环的当前阶段如何。这里的一个 * 操作* 被视作为一个从底层 C/C++ 处理器开始过渡,并且处理需要执行的 JavaScript 代码 。
201+ 您可能已经注意到 ` process.nextTick() ` 在图示中没有显示,即使它是异步 API 的一部分。这是因为 ` process.nextTick() ` 从技术上讲不是事件循环的一部分。相反,它都将在当前操作完成后处理 ` nextTickQueue ` , 而不管事件循环的当前阶段如何。这里所谓的 * 操作* 被定义为来自底层 C/C++ 处理器的转换,和需要处理的 JavaScript 代码的执行 。
202202
203203回顾我们的图示,任何时候在给定的阶段中调用 ` process.nextTick() ` ,所有传递到 ` process.nextTick() ` 的回调将在事件循环继续之前解析。这可能会造成一些糟糕的情况,因为** 它允许您通过递归 ` process.nextTick() ` 调用来“饿死”您的 I/O** ,阻止事件循环到达 ** 轮询** 阶段。
204204
205205### 为什么会允许这样?
206206
207- 为什么这样的事情会包含在 Node.js 中?它的一部分是一个设计理念,其中 API 应该始终是异步的,即使它不必是 。以此代码段为例:
207+ 为什么这样的事情会包含在 Node.js 中?一部分因为它是一个设计理念,即尽管不是必需的情况下, API 应该始终是异步的。以此代码段为例:
208208
209209``` js
210210function apiCall (arg , callback ) {
@@ -216,9 +216,9 @@ function apiCall(arg, callback) {
216216}
217217```
218218
219- 代码段进行参数检查。如果不正确,则会将错误传递给回调函数。最近对 API 进行了更新 ,允许传递参数给 ` process.nextTick() ` ,这将允许它接受任何在回调函数位置之后的参数,并将参数传递给回调函数作为回调函数的参数,这样您就不必嵌套函数了。
219+ 代码段进行参数检查。如果不正确,则会将错误传递给回调函数。这个 API 最近进行了更新 ,允许传递参数给 ` process.nextTick() ` ,这将允许它接受任何在回调函数位置之后的参数,并将参数传递给回调函数作为回调函数的参数,这样您就不必嵌套函数了。
220220
221- 我们正在做的是将错误传回给用户,但仅在执行用户的其余代码之后。通过使用` process.nextTick() ` ,我们保证 ` apiCall() ` 始终在用户代码的其余部分* 之后* 和在让事件循环继续进行* 之前* , 执行其回调函数。为了实现这一点,JS 调用栈被允许展开,然后立即执行提供的回调,允许进行递归调用 ` process.nextTick() ` ,而不触碰 ` RangeError: 超过 V8 的最大调用堆栈大小 ` 限制。
221+ 我们正在做的是将错误传回给用户,但仅在执行用户的其余代码之后。通过使用` process.nextTick() ` ,我们保证 ` apiCall() ` 始终在用户代码的其余部分* 之后* 和在让事件循环继续进行* 之前* 执行其回调函数。为了实现这一点,JS 调用栈被允许展开,然后立即执行提供的回调,允许进行递归调用 ` process.nextTick() ` ,而不触碰 ` RangeError: 超过 V8 的最大调用堆栈大小 ` 限制。
222222
223223这种设计原理可能会导致一些潜在的问题。
224224以此代码段为例:
0 commit comments