setImmediate vs nextTick vs setTimeout(fn, 0)
发布于 10 年前 作者 steamwheedle 13499 次浏览 最后一次编辑是 8 年前 来自 分享

setImmediate vs nextTick vs setTimeout(fn, 0)

@tpsw:还有很多理解不到位的地方,欢迎拍砖。


事件循环

Node.js的特点是事件循环,其中不同的事件会分配到不同的事件观察者身上,比如idle观察者,定时器观察者,I/O观察者等等,事件循环每次循环称为一次Tick,每次Tick按照先后顺序从事件观察者中取出事件进行处理。

先看个Node.js v0.9版本的事件循环示意图:

687474703a2f2f68746d6c352e6f687473752e6f72672f6e65775f736574496d6d6564696174655f73656d616e746963732e706e67 (1).png

特别注意process.nextTick在Node.js v0.10后从事件循环中移除了。此图的作者是来自Github的@shigeki

setImmediate vs process.nextTick

// https://github.com/senchalabs/connect/blob/master/lib/proto.js#L26
var defer = typeof setImmediate === 'function'
  ? setImmediate
  : function(fn){ process.nextTick(fn.bind.apply(fn, arguments)) }

上面的代码片段来自connect.js最近一次提交的修改,备注内容:Correctly invoke async callback asynchronously

阅读Faster process.nextTick小节:http://blog.nodejs.org/2013/03/11/node-v0-10-0-stable/

setImmediate()是Node.js v10.0新增的API,任何方法如果你想要异步调用,都建议使用setImmediate()来操作。

两者的区别:

  • setImmediate()属于check观察者,其设置的回调函数,会插入到下次事件循环的末尾。

  • process.nextTick()设置的回调函数,会在代码运行完成后立即执行,会在下次事件循环之前被调用,原文是 “the callback will fire as soon as the code runs to completion, but before going back to the event loop.”

  • process.next()所设置的回调函数会存放到数组中,一次性执行所有回调函数。

  • setImmediate()所设置的回调函数会存到到链表中,每次事件循环只执行链表中的一个回调函数。

setTimeout(fn, 0) vs setImmediate


setInterval(function() {
    setTimeout(function() {
        console.log('setTimeout3');
    }, 0);

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

    console.log('console1');

    process.nextTick(function() {
        console.log('nextTick2');
    });
}, 100)

// console1
// nextTick2
// setImmediate4
// setTimeout3

// console1
// nextTick2
// setTimeout3
// setImmediate4

执行上面的代码,你基本能看到注释里的这两种结果。为什么会出现这种情况,按照我们前面的事件循环的示意图,setTimeout()的优先级大于setImmediate(),为什么setImmediate()还会先于setTimeout(fn, 0)执行呢?

参考 Issuses-6034,Node.js核心作者TJ的解释:

timers are based on a time in the future, even if it’s 0, while check immediate is always on the next turn of the loop. So it’s possible that the delay of the event loop is low enough for the timer to fire after the immediate.

You shouldn’t necessarily be concerned about the order of operations in this regard though in my estimation.

我们再到setTimeout()的源代码里看看究竟:

// https://github.com/joyent/node/blob/master/lib/timers.js#L200

exports.setTimeout = function(callback, after) {
  var timer;

  after *= 1; // coalesce to number or NaN

  if (!(after >= 1 && after <= TIMEOUT_MAX)) {
    after = 1; // schedule on next tick, follows browser behaviour
  }
	
  // ...
}

执行setTimeout(fn, 0)其实就是在执行setTimeout(fn, 1),也就是说setImmediate()是有可能先于setTimeout(fn, 0)执行的。

3 回复

其实这个vs没有必要拉进来process.nextTick()这个毋庸置疑的优先级最高,核心点在于setTimeout(0)和setImmediate(),而setTimeout的创建要涉及到创建红黑树等性能消耗,所以如果是想创建异步操作的话毋庸置疑是使用setImmediate,但是如果硬要比较他们俩谁先执行,个人感觉没有必要。毕竟setTimeout(0)的目的就是创建异步。

@haozxuan 哈哈哈,是的,直接用setImmediate()就好,但之前用的稀里糊涂的。

setTimeout的创建要涉及到创建红黑树等性能消耗 , 这句话如何验证?看源码吗?

回到顶部