Skip to content

Instantly share code, notes, and snippets.

@tjfontaine
Last active February 16, 2016 06:49
Show Gist options
  • Save tjfontaine/c789adc4df3fe9064279 to your computer and use it in GitHub Desktop.
Save tjfontaine/c789adc4df3fe9064279 to your computer and use it in GitHub Desktop.
Description of Deferrals in Node.js

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 nextTicks 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment