You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
constfs=require('fs');functionaddNextTickRecurs(count){letself=this;if(self.id===undefined){self.id=0;}if(self.id===count)return;process.nextTick(()=>{console.log(`process nextTick call ${++self.id}`);addNextTickRecurs.call(self,count);});}addNextTickRecurs(Infinity);setTimeout(console.log.bind(console,'omg! setTimeout was called'),10);setImmediate(console.log.bind(console,'omg! setImmediate also was called'));fs.readFile(__filename,()=>{console.log('omg! file read complete callback was called');});console.log('started');
当定时器或者 interval 被设置了一个特定时间间隔,但是并不能保证定时器的回调在到了这个时间间隔后会被立刻执行。定时器回调的执行时机与系统的性能(Node 在执行回调函数之前会去检查定时器是否已经到期,这消耗了一定的 CPU 性能),还有当前事件循环正在运行的进程有关。所以,设置的时间只能保证定时器的回调不会在这个时间间隔内被执行,我们可以通过下面这个简单的程序来模拟:
const start = process.hrtime();
setTimeout(() => {
const end = process.hrtime(start);
console.log(`timeout callback executed after ${end[0]}s and ${end[1]/Math.pow(10, 9)}ms`);
}, 1000);
上面的程序将会在运行的时候新建一个过期时间为 1000ms 的定时器,并且打印出它用了多久才执行这个函数。如果你多次运行这个程序,你将会注意到每次的结果都不一样,并且它永远不会打印出 timeout callback executed after 1s and 0ms。你将会得到以下结果:
timeout callback executed after 1s and 0.006058353ms
timeout callback executed after 1s and 0.004489878ms
timeout callback executed after 1s and 0.004307132ms
...
setImmediate(() => console.log('this is set immediate 1'));
setImmediate(() => console.log('this is set immediate 2'));
setImmediate(() => console.log('this is set immediate 3'));
setTimeout(() => console.log('this is set timeout 1'), 0);
setTimeout(() => {
console.log('this is set timeout 2');
process.nextTick(() => console.log('this is process.nextTick added inside setTimeout'));
}, 0);
settimeout(() => console.log('this is set timeout 3'), 0);
settimeout(() => console.log('this is set timeout 4'), 0);
settimeout(() => console.log('this is set timeout 5'), 0);
process.nextTick(() => console.log('this is process.nextTick 1'));
process.nextTick(() => {
process.nextTick(console.log.bind(console, 'this is the inner next tick inside next tick'));
});
process.nextTick(() => console.log('this is process.nextTick 2'));
process.nextTick(() => console.log('this is process.nextTick 3'));
process.nextTick(() => console.log('this is process.nextTick 4'));
在执行上面的脚本之后,以下的事件将会被添加到事件循环队列中。
3 immediates
5 timer callbacks
5 next tick callbacks
来一起看一下执行的过程:
当事件循环开始的时候,它将会注意到 next tick 队列不为空,并且开始处理 next tick 队列的函数。在执行第二个 next tick 回调的时候,一个新的 next tick 回调函数被添加到 next tick 队列的最后,它将会在 next tick 队列中最后被执行。
然后事件循环进入 timmer 阶段,开始执行到期定时器添加到队列中的回调,在第二个定时器的回调中,一个 next tick 回调函数被添加到 next tick 队列中。
当定时器队列的所有回调都被执行之后,事件循环发现 next tick 队列有一个待处理的回调即刚刚定时器回调中添加的那个 next tick 回调函数,然后事件循环就会执行这个函数。
this is process.nextTick 1
this is process.nextTick 2
this is process.nextTick 3
this is process.nextTick 4
this is the inner next tick inside next tick
this is set timeout 1
this is set timeout 2
this is set timeout 3
this is set timeout 4
this is set timeout 5
this is process.nextTick added inside setTimeout
this is set immediate 1
this is set immediate 2
this is set immediate 3
zhangxiang958
changed the title
[译]定时器,Immediates 和 *process.nextTick*——NodeJS 事件循环 Part 2
[译]定时器,Immediates 和 process.nextTick——NodeJS 事件循环 Part 2
Feb 16, 2019
原文链接:https://jsblog.insiderattack.net/timers-immediates-and-process-nexttick-nodejs-event-loop-part-2-2c53fd511bb3
欢迎回到事件循环系列文章!在第一篇文章中,我讲述了 NodeJS 事件循环的整体概述。在本篇文章中,我将会深入使用样例代码讲述的三个重要的事件队列中的细节。它们是 timers 定时器,immediates,和 process.nextTick 回调函数。
文章系列目录
Next Tick 队列
我们来回顾一下上一篇文章看到的关于事件循环的图。
Next Tick 队列分别在其他四个主要的队列执行间隙中执行,因为它并不是 libuv 原生提供的,而是由 Node 实现的。
在事件循环每个阶段之前(处理定时器队列,IO 事件队列,immediates 队列,close 事件处理队列是四个主要的事件处理阶段),也就是在移动到下一个阶段前,Node 会检查 nextTick 队列是否有待处理的函数。如果有,那么 Node 将会立即开始处理队列中的函数,直到队列为空,在每次准备移动到下一个事件循环阶段都会进行这样的处理。
这样带来了一个新的问题。如果我们不断地递归地使用 process.nextTick 将回调函数添加到 nextTick 队列中,将会引起 I/O 和其他队列永远不会被处理执行。我们可以通过下面这个简单的脚本来模拟这样的情况:
你可以看到 nextTick 的回调被不断调用打印出信息,但是 setTimeout,setImmediate,和 fs.readFile 回调则永远不会被调用因为看不到有 'omg!....' 开头的打印信息。
你可以尝试给 addNextTickRecurs 函数设置一个有限的值,然后你将会看到 setTimeout,setImmediate,和 fs.readFile 回调函数在 process.nextTick 添加的所有回调函数执行完后被执行。
Timers 队列
当你使用 setTimeout 设置定时器或者使用 setInterval 设置一个不断调用的定时器的时候,Node 将会把这些添加的定时器函数添加到 libuv 中的一个称为定时器堆(timer heap)的数据结构中。在 Node 的事件循环定时器阶段中,Node 将会检查定时器堆(timer heap)中是否有到期的定时器或 interval,并分别执行它们的回调函数。如果该时刻有不止一个定时器需要被执行,那么它们将会按照被添加到队列的顺序一一被执行。
当定时器或者 interval 被设置了一个特定时间间隔,但是并不能保证定时器的回调在到了这个时间间隔后会被立刻执行。定时器回调的执行时机与系统的性能(Node 在执行回调函数之前会去检查定时器是否已经到期,这消耗了一定的 CPU 性能),还有当前事件循环正在运行的进程有关。所以,设置的时间只能保证定时器的回调不会在这个时间间隔内被执行,我们可以通过下面这个简单的程序来模拟:
上面的程序将会在运行的时候新建一个过期时间为 1000ms 的定时器,并且打印出它用了多久才执行这个函数。如果你多次运行这个程序,你将会注意到每次的结果都不一样,并且它永远不会打印出 timeout callback executed after 1s and 0ms。你将会得到以下结果:
原生的定时器 setTimeout 和 setImmediate 一起使用的话,它的执行时机是不可预测的,造成不可预知的结果。我将会在后面章节讲解。
Immediates 队列
尽管 immediate 队列在表现行为上与定时器队列很相似,但是它还是有一些不同的地方。不像定时器即使设置的过期时间为 0 也不能保证其执行的时机,immediate 队列可以保证在事件循环中在 I/O 阶段之后一定会被立即执行。可以使用 setImmediate 这个函数来将回调添加到队列中:
setTimeout vs setImmediate ?
现在,当我们看一下文章开始的关于事件循环的图,你可以发现程序的执行时机,Node 是从定时器开始处理,然后处理 I/O,然后再处理 immediate 队列。通过上面那幅图,我们可以轻易地推断出下面程序的结果:
你可能会猜想,这个程序运行后会先打印 setTimeout 然后再打印 setImmediate 因为到期的定时器会比 immediate 队列先执行。但是这个程序的结果是不可预料的!如果你多次运行这个程序,你会得到不一样的结果。
这是因为为一个定时器的过期时间设置为 0 无法保证这个定时器的回调一定会在 0 秒后被执行。因为这个原因,当事件循环开始的时候,它可能没有检测到这个到期的定时器,然后事件循环就移动到了下一个阶段即 I/O 阶段 然后到了 immediate 阶段。然后事件循环发现 immediate 队列中有待处理的程序然后就执行它。
但是我们看一下下面的程序,我们可以保证 immediate 的回调函数一定会比定时器的回调先执行:
来一起看一下这个程序的执行流程。
结论
所以,我们来看一下以下例子,看一下在事件循环的不同阶段不同的队列是如何协作:
在执行上面的脚本之后,以下的事件将会被添加到事件循环队列中。
来一起看一下执行的过程:
很好!如果你执行过上面的代码,你将会得到以下的输出:
在下一篇文章中,我将会讲述 next tick 回调与 resolved Promise。
如果对本篇文章有任何意见或建议,请踊跃评论。
References:
The text was updated successfully, but these errors were encountered: