There are four kinds of deferral mechanisms in Node.js:
setTimeout
setInterval
setImmediate
process.nextTick
setTimeout
and setInterval
are quite familiar to those used to JavaScript in the browser and their semantics are fairly well understood. They return opaque values that can be passed to their clear
counterparts, and have been around forever. setImmediate
is a newer construct and its adoption in the browser is not very wide, and nextTick
is a creation solely unto Node.js. The latter two are mechanisms to queue a callback in the short future, such that currently executing JavaScript may continue. If you're used to trying to do this pattern in the browser you may be used to using something like setTimeout(fn, 0)
.
If Node.js actually exposed the idea of a turn of the event loop, you would be expecting the scheduled callback to run at the end of the current loop, or the start of the next -- though from the perspective of your application there isn't really a difference. In fact, the actual contract that any of these deferrals provide from a consumer aspect, is that they guarantee your callback will be called in the future, and that the order in which you enqueued them, will be the order in which they are executed.
There is absolutely no contract from Node.js about if setTimeout
, setInterval
, setImmediate
, or nextTick
's will fire in a particular order amongst themselves. Applications relying on ordering of those callbacks are inherently broken. Don't do that.
The difference between setImmediate
and setTimeout(fn, 0)
is subtle, and for most people difference without distinction. There is a difference between how setImmediate
works in v0.10 and v0.12+ though which is important. In v0.10 only one queued callback per turn of the event loop is executed, whereas on v0.12 the list is spliced and the entire list of queued callbacks are executed in order every turn of the event loop.
The reason setImmediate
exists and behaves the way it does though, is to avoid a problem in the current implementation of nextTick
that is sometimes referred to as recursive nextTicks
. That actually is a misnomer though. What we're referring to here is scheduling of a nextTick
within a currently executing nextTick
.
Both nextTick
and setImmediate
implementations are similar, every time you call one with the provided callback, that function is added to the end of the list, and execution of the JavaScript resumes. Eventually it comes time to clear the queued callbacks. Since nextTick
does not splice the list of queued callbacks, if you enqueue another function during an executing nextTick
callback you will actually be turning what you think is asynchronous code into synchronous.
Frustrating I know.
Consider the following:
var prog = setInterval(function() { console.log("progress") }, 0);
var arr = [];
for (var x = 0; x < 1e2; x++)
arr.push(x);
var i = 0;
function p() {
if (i > arr.length) {
clearInterval(prog);
return;
}
console.log(arr[i++]);
process.nextTick(p);
}
process.nextTick(p);
Given the workflow here, you would probably expect to actually iterate this array relatively asynchronously. However, when you run this progress
never prints only 0..99
before node exits.
Now changing the nextTick
to setImmediate
you will actually get the interval
to make forward progress.
var prog = setInterval(function() { console.log("progress") }, 0);
var arr = [];
for (var x = 0; x < 1e2; x++)
arr.push(x);
var i = 0;
function p() {
if (i > arr.length) {
clearInterval(prog);
return;
}
console.log(arr[i++]);
setImmediate(p);
}
setImmediate(p);
In lighter code, the implementation details look something like this:
for nextTick
:
var list = [];
function nextTick(cb) {
list.push(cb);
}
function runNextTickQueue() {
while(list.length) {
var cb = list.pop();
cb();
}
}
and for setImmediate
:
var list = [];
function setImmediate(cb) {
list.push(cb);
}
function runImmediateQueue() {
var oldlist = list;
list = [];
while (oldlist.length) {
var cb = oldlist.pop();
cb();
}
}
It's a subtle, yet important implementation difference. Trying to iterate asynchronously with a nextTick
can basically result in you doing while(true) { //spin }
, which is almost certainly not what you meant to do.
You probably won't find yourself making such an obvious mistake, but it can happen when combining your application with other modules or software executing callbacks in nextTick
s that you weren't aware of.
Consider the following:
var EE = require('events').EventEmitter;
var util = require('util');
function Foo() {
EE.call(this);
}
util.inherits(Foo, EE);
Foo.prototype.doIt = function doIt() {
var self = this;
process.nextTick(function() {
self.emit('error', new Error('fail'));
});
};
var f = new Foo();
f.on('error', function () {
process.nextTick(function () {
f.doIt();
});
console.log('trying again');
});
f.doIt();
The interaction here results in node either getting stuck on cpu, or having a stack overflow.
Either way it's bad.
So, long story short, only ever use setImmediate
or setTimeout(fn, 0)
and never use process.nextTick