setImmediate 与 nextTick

IT技术 javascript node.js setimmediate
2021-01-22 15:00:51

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

MDN 的说法来看,它似乎与process.nextTick.

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

6个回答

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

因此,如果您尝试使用递归分解长时间运行的、受 CPU 限制的作业,您现在想要使用setImmediate而不是process.nextTick将下一次迭代排队,否则任何 I/O 事件回调都不会有机会在迭代之间运行。

@UstamanSangat setImmediate 仅受 IE10+ 支持,所有其他浏览器都顽固地拒绝实施可能的未来标准,因为他们不喜欢被微软打败。要在 FF/Chrome 中实现类似的结果,您可以使用 postMessage(将消息发布到您自己的窗口)。您也可以考虑使用 requestAnimationFrame,尤其是当您的更新与 UI 相关时。setTimeout(func, 0) 根本不像 process.nextTick 那样工作。
2021-03-28 15:00:51
@fabspro 但不幸的是,该函数被称为 nextTick。nextTick “立即”执行,而 setImmediate 更像是 setTimeout/postMessage。
2021-03-28 15:00:51
传递给 process.nextTick 的回调通常会在当前执行流程结束时被调用,因此大约与同步调用函数一样快。如果不加以检查,这将使事件循环饿死,从而阻止任何 I/O 发生。setImmediates 按照创建的顺序排队,并在每次循环迭代时从队列中弹出一次。这与 process.nextTick 不同,后者将在每次迭代中执行 process.maxTickDepth 排队回调。setImmediate 将在触发排队回调后让步给事件循环,以确保 I/O 没有被饿死。
2021-03-30 15:00:51
@CraigAndrews 我会避免,requestAnimationFrame因为它并不总是发生(我肯定已经看到了这一点,我认为示例是选项卡不是当前选项卡)并且它可以在页面完成绘制之前被调用(即浏览器仍在忙于绘制)。
2021-03-31 15:00:51
@fabspro“因为他们不喜欢被我的微软打败”让你听起来对某事感到痛苦。这主要是因为它的名字太可怕了。如果有一次 setImmediate 函数永远不会运行,它会立即运行。函数的名称与它的作用完全相反。nextTick 和 setImmediate 最好切换一下;setImmediate 在当前堆栈完成后(在等待 I/O 之前)立即执行,而 nextTick 在下一个滴答结束时(在等待 I/O 之后)执行。不过话说回来,这句话已经说了一千遍了。
2021-04-03 15:00:51

举例说明:

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('setTimeout 1'), 0);
    setImmediate(() => console.log('setImmediate 1'));
    process.nextTick(() => console.log('nextTick 1'));
    setImmediate(() => console.log('setImmediate 2'));
    process.nextTick(() => console.log('nextTick 2'));
    http.get(options, () => console.log('network IO'));
    fs.readdir(process.cwd(), () => console.log('file system IO 1'));
    setImmediate(() => console.log('setImmediate 3'));
    process.nextTick(() => console.log('nextTick 3'));
    setImmediate(() => console.log('setImmediate 4'));
    fs.readdir(process.cwd(), () => console.log('file system IO 2'));
    console.log('End');
    setTimeout(done, 1500);
  });
});

将给出以下输出

Start // synchronous
End // synchronous
nextTick 1 // microtask
nextTick 2 // microtask
nextTick 3 // microtask
setTimeout 1 // macrotask
file system IO 1 // macrotask
file system IO 2 // macrotask
setImmediate 1 // macrotask
setImmediate 2 // macrotask
setImmediate 3 // macrotask
setImmediate 4 // macrotask
network IO // macrotask

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

更新:

process.nextTick()在触发任何其他 I/O 事件之前使用run延迟回调,而使用 setImmediate(),执行将排在队列中已经存在的任何 I/O 事件之后。

Node.js Design Patterns,作者 Mario Casciaro(可能是关于 node.js/js 的最好的书)

我认为重要的是要指出 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: 所以,这个答案并没有真正回答确切的区别,而只是一个在不同上下文中可能会有所不同的例子
2021-03-13 15:00:51
这真的很有帮助,谢谢。我认为图像和示例是理解事物的最快方式。
2021-03-18 15:00:51
正如@Actung 所指出的。了解 setTimetout 和 setImmediate 是否在 I/O 周期内对确定结果非常重要。
2021-03-19 15:00:51
为什么最后是I01?
2021-03-27 15:00:51

我想我可以很好地说明这一点。由于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迭代为零函数。然后它将注册两个处理程序,一个 forsetImmediate和一个 for 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 的成熟和人们开始依赖现有接口而得到的。

谢谢,这是最好的解释。示例代码真的很有帮助。
2021-03-14 15:00:51
很好的解释(Y)
2021-03-17 15:00:51
@skyhavoc 很高兴我能帮上忙!
2021-03-19 15:00:51
重申 Node 关于使用 process.nextTick 的警告很重要。如果您在 nextTickQueue 中排队大量回调,您可能会通过确保永远不会到达轮询阶段来使事件循环挨饿。这就是为什么您通常应该更喜欢 setImmediate 的原因。
2021-03-21 15:00:51
很清楚的描述。我认为这个答案需要更多的赞成。
2021-03-28 15:00:51

在答案的评论中,它没有明确说明 nextTick 从宏语义转向微语义。

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

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

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

简单来说, 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

简化的事件循环事件