setImmediate与nextTick

Node.js版本0.10今天发布并引入了setImmediate 。 API更改文档build议在执行recursionnextTick调用时使用它。

从MDN看来,它与process.nextTick非常相似。

什么时候应该使用nextTick ,什么时候应该使用setImmediate

如果要将函数在已经在事件队列中的任何I / O事件callback后面排队,请使用setImmediate 。 使用process.nextTick有效地将函数排在事件队列头部,以便在当前函数完成后立即执行。

所以在你试图用recursion分解长时间运行的CPU绑定作业的情况下,你现在想要使用setImmediate而不是process.nextTick来排队下一个迭代,否则任何I / O事件callback都不会。没有机会在迭代之间运行。

在答案的评论中,并没有明确说明nextTick从Macrosmantics转移到Microsemantics。

在节点0.9之前(当setImmediate被引入时),nextTick在下一个callstack的开始处被操作。

从节点0.9开始,nextTick在现有的调用堆栈的末尾运行,而setImmediate在下一个调用堆栈的开始

查看https://github.com/YuzuJS/setImmediate获取工具和细节;

作为例证

 import fs from 'fs'; import http from 'http'; const options = { host: 'www.stackoverflow.com', port: 80, path: '/index.html' }; describe('deferredExecution', () => { it('deferredExecution', (done) => { console.log('Start'); setTimeout(() => console.log('TO1'), 0); setImmediate(() => console.log('IM1')); process.nextTick(() => console.log('NT1')); setImmediate(() => console.log('IM2')); process.nextTick(() => console.log('NT2')); http.get(options, () => console.log('IO1')); fs.readdir(process.cwd(), () => console.log('IO2')); setImmediate(() => console.log('IM3')); process.nextTick(() => console.log('NT3')); setImmediate(() => console.log('IM4')); fs.readdir(process.cwd(), () => console.log('IO3')); console.log('Done'); setTimeout(done, 1500); }); }); 

会给出以下输出

 Start Done NT1 NT2 NT3 TO1 IO2 IO3 IM1 IM2 IM3 IM4 IO1 

我希望这可以帮助理解差异。

我想我可以很好地说明这一点。 由于在当前操作结束时调用nextTick ,所以recursion调用可能会阻止事件循环的继续。 setImmediate通过在事件循环的检查阶段触发来解决这个问题,从而允许事件循环正常继续。

  ┌───────────────────────┐ ┌─>│ timers │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ I/O callbacks │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ idle, prepare │ │ └──────────┬────────────┘ ┌───────────────┐ │ ┌──────────┴────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └──────────┬────────────┘ │ data, etc. │ │ ┌──────────┴────────────┐ └───────────────┘ │ │ check │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ └──┤ close callbacks │ └───────────────────────┘ 

来源: https : //nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/

请注意,检查阶段在投票阶段之后立即进行。 这是因为投票阶段和I / Ocallback是您最有可能立即调用setImmediate地方。 所以理想情况下,大部分调用实际上是非常直接的,只是不如每个操作之后检查的nextTick那样直接,并且技术上在事件循环之外存在。

我们来看一下setImmediateprocess.nextTick之间区别的一个例子:

 function step(iteration) { if (iteration === 10) return; setImmediate(() => { console.log(`setImmediate iteration: ${iteration}`); step(iteration + 1); // Recursive call from setImmediate handler. }); process.nextTick(() => { console.log(`nextTick iteration: ${iteration}`); }); } step(0); 

比方说,我们刚刚运行这个程序,正在逐步完成事件循环的第一次迭代。 它会调用迭代零的step函数。 然后它将注册两个处理程序,一个用于setImmediate ,一个用于process.nextTick 。 然后,我们从setImmediate处理程序recursion地调用这个函数,它将在下一个检查阶段运行。 nextTick处理程序将在当前操作结束时运行,从而中断事件循环,因此即使它被注册了,它实际上也会先运行。

顺序结束为: nextTick在当前操作结束时触发,下一个事件循环开始,正常的事件循环阶段执行, setImmediate触发并recursion调用我们的step函数以重新开始整个过程​​。 当前操作结束, nextTick触发等。

上面的代码的输出是:

 nextTick iteration: 0 setImmediate iteration: 0 nextTick iteration: 1 setImmediate iteration: 1 nextTick iteration: 2 setImmediate iteration: 2 nextTick iteration: 3 setImmediate iteration: 3 nextTick iteration: 4 setImmediate iteration: 4 nextTick iteration: 5 setImmediate iteration: 5 nextTick iteration: 6 setImmediate iteration: 6 nextTick iteration: 7 setImmediate iteration: 7 nextTick iteration: 8 setImmediate iteration: 8 nextTick iteration: 9 setImmediate iteration: 9 

现在,让我们将recursion调用移入nextTick处理程序,而不是setImmediate

 function step(iteration) { if (iteration === 10) return; setImmediate(() => { console.log(`setImmediate iteration: ${iteration}`); }); process.nextTick(() => { console.log(`nextTick iteration: ${iteration}`); step(iteration + 1); // Recursive call from nextTick handler. }); } step(0); 

现在我们已经将recursion调用移到nextTick处理程序中,事情将以不同的顺序运行。 我们的事件循环的第一次迭代运行并调用注册setImmedaite处理程序以及nextTick处理程序的步骤。 在当前操作结束之后,我们的nextTick处理程序触发recursion调用step并注册另一个setImmediate处理程序以及另一个nextTick处理程序。 由于在当前操作之后触发nextTick处理程序,因此在nextTick处理程序中注册nextTick处理程序将导致第二个处理程序在当前处理程序操作完成后立即运行。 nextTick处理程序将继续触发,防止当前的事件循环继续。 在我们看到一个setImmediate处理程序触发之前,我们将通过所有的nextTick一个setImmediate处理程序。

上面代码的输出结果是:

 nextTick iteration: 0 nextTick iteration: 1 nextTick iteration: 2 nextTick iteration: 3 nextTick iteration: 4 nextTick iteration: 5 nextTick iteration: 6 nextTick iteration: 7 nextTick iteration: 8 nextTick iteration: 9 setImmediate iteration: 0 setImmediate iteration: 1 setImmediate iteration: 2 setImmediate iteration: 3 setImmediate iteration: 4 setImmediate iteration: 5 setImmediate iteration: 6 setImmediate iteration: 7 setImmediate iteration: 8 setImmediate iteration: 9 

请注意,如果我们没有中断recursion调用并在10次迭代之后中止,那么nextTick调用将继续recursion,并且不会让事件循环继续到下一个阶段。 这就是nextTick在recursion使用时如何被阻塞,而setImmediate将在下一个事件循环中触发,并且从另一个中设置另一个setImmediate处理器根本不会中断当前的事件循环,从而允许继续执行事件循环的阶段。

希望有所帮助!

PS – 我同意其他评论者,这两个函数的名称可以很容易地交换,因为nextTick听起来像是要在下一个事件循环中触发,而不是当前循环的结束,而且当前循环的结尾更“立即“比下一个循环的开始。 哦,那就是我们在API成熟时得到的结果,人们依赖于现有的接口。