367

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

根据MDN 所说,它似乎与process.nextTick.

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

4

9 回答 9

547

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

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

于 2013-03-11T22:26:03.540 回答
93

举例说明:

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

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

更新:

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

Node.js 设计模式,Mario Casciaro(可能是关于 node.js/js 的最佳书籍)

于 2017-02-03T18:38:58.777 回答
65

我想我可以很好地说明这一点。由于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迭代为零的函数。然后它会注册两个处理程序,一个是 for setImmediate,一个是 for process.nextTicksetImmediate然后我们从将在下一个检查阶段运行的处理程序中递归调用此函数。处理程序将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 的成熟和人们开始依赖现有接口的结果。

于 2017-05-31T03:19:34.637 回答
30

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

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

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

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

于 2015-01-27T01:44:10.470 回答
11

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

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

简化的事件循环事件

于 2017-12-29T10:05:38.773 回答
11

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

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

我应该什么时候使用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()”。

于 2019-03-15T04:25:38.110 回答
3

我建议您查看专用于 Loop 的文档部分以获得更好的理解。从那里截取的一些片段:

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

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

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

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

于 2018-07-21T17:04:38.260 回答
2

您永远不应该使用process.nextTick()

你永远不应该使用 process.nextTick() 来分解这样的工作。这样做会导致一个永远不会清空的微任务队列——你的应用程序将永远被困在同一个阶段!——托马斯·亨特。使用 Node.js 的分布式系统

事件循环的每个阶段都包含几个回调:

循环事件的一阶段

一旦process.nextTick()发射,它将始终保持在同一阶段。

例子

const nt_recursive = () => process.nextTick(nt_recursive);
nt_recursive(); // setInterval will never run

const si_recursive = () => setImmediate(si_recursive);
si_recursive(); // setInterval will run

setInterval(() => console.log('hi'), 10);

在此示例中,setInterval()表示应用程序执行的一些异步工作,例如响应传入的 HTTP 请求。

一旦nt_recursive()函数运行,应用程序最终会得到一个永远不会清空的微任务队列,并且异步工作永远不会得到处理。

但替代版本si_recursive()没有相同的副作用。

在检查阶段进行setImmediate()调用会将回调添加到下一个事件循环迭代的检查阶段队列,而不是当前阶段的队列。

案例可能会使用process.nextTick

使函数处理全部异步。

function foo(count, callback) {
  if (count <= 0) {
    return process.nextTick(() => callback(new TypeError('count > 0')));
  }
  myAsyncOperation(count, callback);
}

——托马斯·亨特。使用 Node.js 的分布式系统

在这种情况下,使用setImmediate()orprocess.nextTick()都可以;只要确保您不会意外引入递归即可。

于 2021-03-31T01:20:51.267 回答
0

使用process.nextTick(),您的函数将异步调用其回调。延迟的回调process.nextTick()称为微任务(process.nextTick、Promises、 queueMicrotask、MutationObserver),它们在当前操作完成后立即执行,甚至在任何其他 I/O 事件被触发之前。

使用 setImmediate(),在处理完所有 I/O 事件之后的事件循环阶段将执行排队。由于process.nextTick()在任何已调度的 I/O 之前运行,它会执行得更快,但在某些情况下,它也可能无限期延迟任何 I/O 回调的运行(也称为 I/O 饥饿),例如在存在递归调用。如果回调被无限期延迟,就会发生饥饿。这永远不会发生在setImmediate().

UsingsetTimeout(callback, 0)具有与 类似的行为, 使用 调度的setImmediate()回调setImmediate()比使用 调度的回调执行得更快setTimeout(callback,0)

于 2022-01-28T12:27:25.577 回答