nextTick和setImmediate的区别,可视化解释

94

我很困惑nextTick和setImmediate之间的区别。我已经阅读了互联网上关于它们的所有文档,但我仍然不明白它们是如何工作的。

示例:

function log(n) { console.log(n); }

setImmediate

setImmediate(function() {
  setImmediate(function() {
    log(1);
    setImmediate(function() { log(2); });
    setImmediate(function() { log(3); });
  });
  setImmediate(function() {
    log(4);
    setImmediate(function() { log(5); });
    setImmediate(function() { log(6); });
  });
});

//1 2 3 4 5 6

nextTick

process.nextTick(function() {
  process.nextTick(function() {
    log(1);
    process.nextTick(function() { log(2); });
    process.nextTick(function() { log(3); });
  });
  process.nextTick(function() {
    log(4);
    process.nextTick(function() { log(5); });
    process.nextTick(function() { log(6); });
  });
});

//1 4 2 3 5 6

为什么会出现这些结果?请用可视化或非常易于理解的解释进行说明。即使是节点核心开发人员也不同意nextTick和setImmediate应该如何被人们理解。
来源:
- setImmediate vs. nextTick - 为什么setImmediate比nextTick慢得多? - setImmediate并不总是非常立即

8
两次执行的输出结果相同(Node v5.6.0),都是 1 4 2 3 5 6 - Deniz Ozger
两者产生相同的输出 - MechaCode
1
看看那一年,5年前的事情!许多事情可能已经改变了。 - Gabriel Llamas
5个回答

107

考虑以下两个例子:

setImmediate

setImmediate(function A() {
  setImmediate(function B() {
    log(1);
    setImmediate(function D() { log(2); });
    setImmediate(function E() { log(3); });
  });
  setImmediate(function C() {
    log(4);
    setImmediate(function F() { log(5); });
    setImmediate(function G() { log(6); });
  });
});

setTimeout(function timeout() {
  console.log('TIMEOUT FIRED');
}, 0)

// 'TIMEOUT FIRED' 1 4 2 3 5 6
// OR
// 1 'TIMEOUT FIRED' 4 2 3 5 6

nextTick

process.nextTick(function A() {
  process.nextTick(function B() {
    log(1);
    process.nextTick(function D() { log(2); });
    process.nextTick(function E() { log(3); });
  });
  process.nextTick(function C() {
    log(4);
    process.nextTick(function F() { log(5); });
    process.nextTick(function G() { log(6); });
  });
});

setTimeout(function timeout() {
  console.log('TIMEOUT FIRED');
}, 0)

// 1 4 2 3 5 6 'TIMEOUT FIRED'

setImmediate回调函数会在事件循环中的每次迭代中按照它们排队的顺序被触发。所以,在事件循环的第一次迭代中,回调函数A被触发。然后在事件循环的第二次迭代中,回调函数B被触发,然后在事件循环的第三次迭代中,回调函数C被触发,以此类推。这样可以防止事件循环被阻塞,并允许在此期间调用其他I/O或计时器回调(如0ms超时,在第1或第2个循环迭代中触发)。

而nextTick回调函数总是在当前代码执行完毕并且在返回事件循环之前立即触发。在nextTick示例中,我们最终在返回事件循环之前执行所有的nextTick回调函数。由于setTimeout的回调将从事件循环中调用,因此文本“TIMEOUT FIRED”只有在完成每个nextTick回调之后才会输出。


8
好的,最后一段是理解主要区别的关键。 - Gabriel Llamas
你正在运行哪个版本的Node?在Node 0.10.x上,你应该始终在进入事件循环之前运行nextTick回调。 - Dave Stibrany
24
我希望他们选择一个不同的名称。现在,“nextTick”比“setImmediate”更为即时。 - Jelle De Loecker
说实话,如果process.nextTick不返回到事件循环,那它还有什么意义。如果我想要调用下一个函数,那我就会直接按名称显式调用它。难怪process.nextTick正在被弃用。我一直以为process.nextTick的功能实际上类似于setImmediate的功能。因此process.nextTick是一个巨大的误称。 - Alexander Mills
1
@StrugglingCoder 因为当 A 执行时,它将设置 B 和 C 为下一个要调度的任务。当 B 执行时,它将设置 D 和 E 为下一个要调度的任务,但是 C 已经被安排在 B 之后执行。请记住,回调被安排或注册的时间与回调实际触发的时间之间存在差异。 - Dave Stibrany
显示剩余6条评论

25

根据Node.js文档,这两个函数的名称被完全交换

setImmediate() (推荐使用)

它会在事件队列中优先执行


process.nextTick() (仅在特殊情况下使用,请参见后面的示例)

它会立即执行,类似于在当前文件的结尾写了一个语句


如果我们有以下代码

setTimeout(function(){
  console.log('Hello world 5'); // It's waiting like a normal person at a queue
}, 0);

setImmediate(function(){
  console.log('Hello world 4'); 
  // It's like get to last and be take care of first 
  // but always after of .nextTick and before of setInterval(, 0)
});

process.nextTick(function(){
   console.log('Hello world 3'); // It's like be at the bottom at this file
});

console.log('Hello world 1');
console.log('Hello world 2');

根据您的要求,以下是视觉解释:

在此输入图片描述

在必须在处理事件之前发出事件时使用process.nextTick()的情况:

const EventEmitter = require('events');
const util = require('util');

function MyEmitter() {
  EventEmitter.call(this);

  // use nextTick to emit the event once a handler is assigned
  process.nextTick(function () {
    this.emit('event');
  }.bind(this));
}
util.inherits(MyEmitter, EventEmitter);

const myEmitter = new MyEmitter();
myEmitter.on('event', function() {
  console.log('an event occurred!');
});

看这个视频,Philip Roberts 给我们提供了一个关于运行时事件循环的绝妙解释,并且看看这个在线事件循环调试器 实时测试事件循环如何工作

来源: https://github.com/nodejs/node/blob/master/doc/topics/the-event-loop-timers-and-nexttick.md#processnexttick-vs-setimmediate


如果您运行第一个代码片段,它不会按照您所述的方式运行 -你好世界1 你好世界2 你好世界3 你好世界5 你好世界4 - Abhishek Chandel

8
我无法复现您使用“setImmediate”的结果。它应该与“nextTick”相同(在我的测试中是这样的),因为在这种情况下,它们做的事情几乎相同。唯一合理的解释是,“setImmediate”在某种程度上是同步的,但实际上并不是。根据NodeJS文档,唯一真正的区别是多个“nextTick”可能在一个循环迭代中触发(取决于“maxTickDepth”),而“setImmediate”每次迭代只触发一次。

6
以下内容将为您提供更清晰的解释。

setImmediate

  1. 在当前的轮询阶段完成后,执行一次脚本。
  2. 它是一个计时器模块函数,计时器函数是全局的,您可以不使用require调用它们。
  3. 可以通过clearImmediate()清除。
  4. 在setTimeout()和setInterval()之前设置回调函数的“立即”执行。

nextTick

  1. 它是NodeJS的进程全局对象函数。
  2. 所有传递给process.nextTick()的回调函数都将在事件循环继续之前解决。
  3. 允许用户处理错误。
  4. 在事件循环继续之前,有助于再次尝试请求。

简单的代码片段。

console.log("I'm First");

setImmediate(function () {
  console.log('Im setImmediate');
});

console.log("I'm Second");

process.nextTick(function () {
  console.log('Im nextTick');
});

console.log("I'm Last");

/*
Output
$ node server.js
I'm First
I'm Second
I'm Last
Im nextTick
Im setImmediate
*/

2
我认为上面的所有答案都已过时,因为在当前版本的nodejs中,我不断得到不同的答案,而且很容易理解。
var log=console.log
log(process.version)

var makeAsyncCall
if(false)
    makeAsyncCall=setImmediate
else
    makeAsyncCall=process.nextTick;

makeAsyncCall(function A () {
    makeAsyncCall(function B() {
        log(1);
        makeAsyncCall(function C() { log(2); });
        makeAsyncCall(function D() { log(3); });
    });
    makeAsyncCall(function E() {
        log(4);
        makeAsyncCall(function F() { log(5); });
        makeAsyncCall(function G() { log(6); });
    });
});
//1
//4
//2
//3
//5
//6
//in both case

阅读完文档https://github.com/nodejs/node/blob/master/doc/topics/the-event-loop-timers-and-nexttick.md#processnexttick-vs-setimmediate后,如果我们从setImmediate开始,应该跟踪check队列,因为它是setImmediate回调所在的地方。 第一次迭代A推入check队列 check队列:[A] 第二次迭代Aqueue中取出以执行
在其执行期间,它将BE放入queue中,然后A完成并开始下一次迭代
check队列:[B,E] 第三次迭代 取出B并推入C D check队列:[E,C,D]
第四次迭代
弹出E并推入F、G
检查队列:[C,D,F,G]
最后
按顺序执行队列中的回调函数
对于nextTick情况,队列的工作方式完全相同,这就是为什么它产生相同结果的原因
不同之处在于:
nextTickQueue将在当前操作完成后处理,而不考虑事件循环的当前阶段
要清楚,事件循环维护多个队列,“check queue”只是其中之一,节点将根据一些规则决定使用哪个队列
但是,对于process.nextTick,它会绕过所有规则并立即执行“nextTick”中的回调函数

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接