setImmediate与nextTick


336

Node.js版本0.10已于今天发布并引入 setImmediate。该API的变化文档建议做递归时使用它nextTick调用。

MDN所说的来看,它与process.nextTick

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


20
有关于这个变化对博客5个段落blog.nodejs.org/2013/03/11/node-v0-10-0-stable

1
从性能基准来看,它看起来nextTicksetImmediate大型计算要快。

10
作为记录,我首先阅读了这五个段落,但是当它并没有真正为我解决任何问题时,仍然停留在这个问题上。接受的答案更加简洁,实际上setImmediate更详细地描述了所做的事情。
2014年

我已经在博客中详细解释了差异。
plafer

GC是否可以在之前运行setImmediate但不能在之前运行nextTick

Answers:


510

setImmediate如果要将函数放在事件队列中已存在的所有I / O事件回调后面,请使用此函数。用于process.nextTick将函数有效地放在事件队列的开头,以便在当前函数完成后立即执行。

因此,在您尝试使用递归分解长时间运行且受CPU限制的作业的情况下,您现在想使用setImmediate而不是process.nextTick将下一个迭代放入队列中,因为否则任何I / O事件回调都不会获得机会在迭代之间运行。


86
传递给process.nextTick的回调通常在当前执行流的结尾被调用,因此,其速度与同步调用一个函数的速度差不多。如果不进行检查,这将使事件循环陷入饥饿状态,从而防止发生任何I / O。setImmediates按创建的顺序排队,并在每次循环迭代时从队列中弹出一次。这与process.nextTick不同,后者将在每次迭代中执行process.maxTickDepth排队的回调。在触发排队的回调以确保I / O不会饿死之后,setImmediate将屈服于事件循环。
Benjamin Gruenbaum 2013年

2
@UstamanSangat setImmediate仅受IE10 +支持,所有其他浏览器都顽固地拒绝实施可能的未来标准,因为它们不喜欢被Microsoft打败。要在FF / Chrome中获得相似的结果,可以使用postMessage(将消息发布到自己的窗口中)。您也可以考虑使用requestAnimationFrame,特别是如果您的更新与UI相关。setTimeout(func,0)根本不像process.nextTick。
fabspro

44
@fabspro“因为他们不喜欢被我的微软击败”,这让您听起来有些痛苦。这主要是因为它的名字非常可怕。如果有一次setImmediate函数永远不会运行,那么它将立即生效。函数的名称与函数的名称完全相反。nextTick和setImmediate最好切换一下;setImmediate在当前堆栈完成后立即执行(在等待I / O之前),nextTick在下一个滴答结束时执行(在等待I / O之后)。但是,这已经被说了一千遍了。
Craig Andrews

4
@fabspro但不幸的是,该函数称为nextTick。当setImmediate更像setTimeout / postMessage时,nextTick会“立即”执行。
罗伯特

1
@CraigAndrews我会避免,requestAnimationFrame因为它并不总是发生(我已经明确地看到了,我认为示例不是选项卡不是当前选项卡),并且可以在页面完成绘制之前调用它(即浏览器仍在忙于绘制)。
robocat 2014年

68

作为说明

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

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

更新:

process.nextTick()在激发任何其他I / O事件之前,将运行延迟回叫,而对于setImmediate(),则将执行排队在队列中已存在的任何I / O事件之后。

Mario Casciaro撰写的Node.js设计模式(可能是有关node.js / js的最好的书)


2
这真的很有帮助,谢谢。我认为图像和示例是理解某些事物的最快方法。
约翰·詹姆斯

1
我认为必须指出,setTimeout()和setImmediate()不在I / O周期内时,其顺序是不确定的,具体取决于进程的性能。nodejs.org/en/docs/guides/event-loop-timers-and-nexttick For example, if we run the following script which is not within an I/O cycle (i.e. the main module), the order in which the two timers are executed is non-deterministic, as it is bound by the performance of the process: 所以,这个回答并没有真正回答确切的差异,但仅仅是一个例子,可以在不同的环境而改变
Actung

正如@Actung所指出的。知道setTimetout和setImmediate是否在I / O周期内对于确定结果非常重要。
Rajika Imal

50

我想我可以很好地说明这一点。由于nextTick在当前操作的末尾被调用,因此以递归方式调用它可能最终阻止事件循环继续进行。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 / O回调最有可能在您的调用setImmediate中运行。因此,理想情况下,这些调用中的大多数实际上实际上是立即调用的,而不是像nextTick每次操作之后检查的那样立即调用的,并且从技术上讲不在事件循环之内。

让我们看一下setImmediate和之间的区别的一个小例子process.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处理程序中递归调用此函数,该处理程序将在下一个检查阶段运行。该nextTick处理程序将在当前操作结束时运行,中断事件循环,因此,即使它已被第二次注册,它实际上也会首先运行。

顺序最终是:nextTick在当前操作结束时触发,下一个事件循环开始,正常事件循环阶段执行,setImmediate触发并递归调用我们的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

现在,让我们继续我们的递归调用step到我们的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);

现在,我们已经将递归调用stepnextTick处理事情会以不同的顺序行为。事件循环的第一个迭代运行并调用step注册setImmedaite处理程序和nextTick处理程序。当前操作结束后,我们的nextTick处理程序将触发并递归调用step并注册另一个setImmediate处理程序以及另一个nextTick处理程序。由于nextTick处理程序在当前操作之后触发,因此nextTicknextTick处理程序中注册处理程序将导致第二个处理程序在当前处理程序操作完成后立即运行。该nextTick处理程序将持续开火,阻止当前事件循环从不断持续。我们将度过所有的时光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

请注意,如果我们没有中断递归调用并在10次迭代后中止它,则这些nextTick调用将继续递归,并且永远不会让事件循环继续进行到下一个阶段。这就是如何nextTick在递归使用时变为阻塞,而setImmediate在下一个事件循环中触发并在一个事件循环中设置另一个setImmediate处理程序的方法则完全不会中断当前事件循环,从而使其能够照常继续执行事件循环的各个阶段。

希望有帮助!

PS-我同意其他评论者的观点,这两个函数的名称可以轻松交换,因为nextTick听起来好像是在下一个事件循环中触发,而不是在当前事件的末尾触发,而当前循环的结束更“直接” ”,而不是下一个循环的开始。哦,这就是随着API的成熟以及人们开始依赖现有接口而获得的。


2
很清楚的描述。我认为这个答案需要更多的支持。
Actung

解释得很好(Y)
Dhiraj Sharma

重申Node关于使用process.nextTick的警告很重要。如果在nextTickQueue中加入大量回调,则可以通过确保从未达到轮询阶段来使事件循环饿死。这就是为什么您通常应该首选setImmediate的原因。
faridcs

1
谢谢,这是最好的解释。示例代码确实有帮助。
skyhavoc

@skyhavoc很高兴能为您提供帮助!
CHEV

30

在答案的注释中,它没有明确声明nextTick从Macrosemantics转变为Microsemantics。

在节点0.9之前(引入setImmediate时),nextTick在下一个调用堆栈的开始处运行。

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

查看https://github.com/YuzuJS/setImmediate以获取工具和详细信息



7

这里有一些很好的答案,详细介绍了它们如何工作。

只需添加一个回答特定问题的答案即可:

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


始终使用setImmediate


Node.js的事件循环,定时器和process.nextTick()文档包括以下内容:

我们建议开发人员setImmediate()在所有情况下都使用它,因为这样更容易推理(并且导致代码与更广泛的环境兼容,例如浏览器JS。)


在文档的早期,它警告process.nextTick可能导致...

有些糟糕的情况,因为它允许您通过进行递归process.nextTick()调用来“饿死”您的I / O,从而防止事件循环到达轮询阶段。

事实证明,process.nextTick甚至可以饿死Promises

Promise.resolve().then(() => { console.log('this happens LAST'); });

process.nextTick(() => {
  console.log('all of these...');
  process.nextTick(() => {
    console.log('...happen before...');
    process.nextTick(() => {
      console.log('...the Promise ever...');
      process.nextTick(() => {
        console.log('...has a chance to resolve');
      })
    })
  })
})

另一方面,setImmediate更易于推理 ”并避免了以下类型的问题:

Promise.resolve().then(() => { console.log('this happens FIRST'); });

setImmediate(() => {
  console.log('this happens LAST');
})

因此,除非特别需要的独特行为,否则process.nextTick建议的方法是“ 在所有情况下使用setImmediate() ”。


1

我建议您检查文档专用于Loop的部分,以更好地理解。从那里摘录的一些片段:

就用户而言,我们有两个呼叫类似,但是它们的名称令人困惑。

  • process.nextTick()在同一阶段立即触发

  • setImmediate()在
    事件循环的以下迭代或“滴答”中触发

本质上,名称应互换。process.nextTick()比setImmediate()更快地触发,但这是过去的产物,不太可能改变。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.