setImmediate vs nextTick vs setTimeout(fn, 0)
@tpsw:还有很多理解不到位的地方,欢迎拍砖。
事件循环
Node.js的特点是事件循环,其中不同的事件会分配到不同的事件观察者身上,比如idle观察者,定时器观察者,I/O观察者等等,事件循环每次循环称为一次Tick,每次Tick按照先后顺序从事件观察者中取出事件进行处理。
先看个Node.js v0.9版本的事件循环示意图:
特别注意
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)
执行的。