setImmediate与nextTick

问题描述 投票:315回答:7

Node.js版本0.10今天发布并引入了setImmediateAPI changes文档建议在进行递归nextTick调用时使用它。

从什么MDN says它看起来非常类似于process.nextTick

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

javascript node.js setimmediate
7个回答
481
投票

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

因此,在您尝试使用递归分解长时间运行的CPU绑定作业的情况下,您现在希望使用setImmediate而不是process.nextTick来排队下一次迭代,否则任何I / O事件回调都不会得到在迭代之间运行的机会。


53
投票

作为例证

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

我希望这有助于理解其中的差异。


38
投票

我想我可以很好地说明这一点。由于在当前操作结束时调用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那样直接,在每次操作之后检查并在技术上存在于事件循环之外。

让我们来看看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处理程序递归调用此函数,该处理程序将在下一个检查阶段运行。 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);

既然我们已经将step的递归调用移动到nextTick处理程序中,那么事情将以不同的顺序运行。我们对事件循环的第一次迭代运行并调用step注册一个setImmedaite处理程序以及一个nextTick处理程序。当前操作结束后,我们的nextTick处理程序触发,递归调用step并注册另一个setImmediate处理程序以及另一个nextTick处理程序。由于nextTick处理程序在当前操作之后触发,因此在nextTick处理程序中注册nextTick处理程序将导致第二个处理程序在当前处理程序操作完成后立即运行。 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成熟时所获得的,人们开始依赖现有的接口。


29
投票

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

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

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

查看https://github.com/YuzuJS/setImmediate的工具和细节


9
投票

简单来说,process.NextTick()将在事件循环的下一个刻度处执行。但是,setImmediate基本上有一个单独的阶段,它确保只在IO回调和轮询阶段之后才会调用在setImmediate()下注册的回调。

请参考此链接以获得更好的解释:https://medium.com/the-node-js-collection/what-you-should-know-to-really-understand-the-node-js-event-loop-and-its-metrics-c4907b19da4c

simplified event loop events


3
投票

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

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

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


始终使用setImmediate


Node.js Event Loop, Timers, and 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的docs部分,以便更好地理解。从那里采取的一些片段:

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

  • process.nextTick()立即在同一阶段触发
  • setImmediate()触发以下迭代或'tick' 事件循环

实质上,应该交换名称。 process.nextTick()比setImmediate()更快地触发,但这是过去的工件,不太可能改变。

© www.soinside.com 2019 - 2024. All rights reserved.