I do hope you're just reading this article for the entertainment value, because if you really need this information it's time to reevaluate your life choices. Leave your job as a node contributor, instrumentation engineer, or whatever other tedious mess you've gotten yourself into, and pursue your dreams. Computers are awful. Work the land, start a woodcraft shoppe, maybe tend bar. Here be dragons. When you have a lot of timers going at once, the when exactly they are going to fire is one of the JavaScript Common Pitfalls.
If you're in Node, `process.nextTick(fn)` takes precedence. The way to think of it is that nextTick doesn't really fire on the next tick, it latches on to the end of the current tick. Preempting other asynchronous operations.
var start = Date.now() function recursiveNextTick () { process.nextTick(recursiveNextTick) } recursiveNextTick() // Neither of these fire because we are // busy nextTicking setImmediate(check('setImmediate 1')) setTimeout(check('setTimeout 1'), 0) function check (name) { return function () { console.log(name + ' time: ' + process.hrtime()) } }
We've starved the event loop with nextTicks and nothing ever happens again, except an endless line of recursive nextTick function calls. Neither setImmediate or setTimeout ever call back.
So, that's lame but it's easy to solve. Simply don't recursively call nextTick.
What about the order of setImmediate vs setTimeout? Well, it depends.
var wait = 0 var start = Date.now() setImmediate(check('setImmediate 1')) setTimeout(check('setTimeout 1'), 0) setImmediate(check('setImmediate 2')) setImmediate(check('setImmediate 3')) setTimeout(check('setTimeout 2'), 0) setTimeout(check('setTimeout 3'), 0) while(true) { if (Date.now() - start > wait) break } function check (name) { return function () { console.log(name + ' time: ' + process.hrtime()) } } // Example output // setTimeout 1 time: 318799,422057454 // setImmediate 1 time: 318799,423598869 // setImmediate 2 time: 318799,424152249 // setImmediate 3 time: 318799,424209859 // setTimeout 2 time: 318799,424280131 // setTimeout 3 time: 318799,424308713
In node 0.10 the timeouts and immediates will interleave themselves in any order. The only guarantee is that for a single timer type they will all end up in order. In node 0.12+ all of the setImmediate's will fire at once, but they could be in between any step of the set timeouts. Now, if we wait for a moment after we set the timers before they fire...
var wait = 10 // Just 10 ms! var start = Date.now() setImmediate(check('setImmediate 1')) setTimeout(check('setTimeout 1'), 0) setImmediate(check('setImmediate 2')) setImmediate(check('setImmediate 3')) setTimeout(check('setTimeout 2'), 0) setTimeout(check('setTimeout 3'), 0) while(true) { if (Date.now() - start > wait) break } function check (name) { return function () { console.log(name + ' time: ' + process.hrtime()) } } // Consistent output // setTimeout 1 time: 319368,882589805 // setTimeout 2 time: 319368,883911971 // setTimeout 3 time: 319368,884485374 // setImmediate 1 time: 319368,884904697 // setImmediate 2 time: 319368,884962995 // setImmediate 3 time: 319368,884991232
All of a sudden all three setTimeouts come in order first, before the setImmediate calls. By adding a little bit of synchronous processing time we ensure that the setTimeout timers are up before the function returns. Since the 'setTimeout' queue has higher precedence than the 'setImmediate' queue, when the timeouts are ready, they preempt the immediates!
We have the potential to introduce race conditions that are not only platform version dependent, but also execution time dependent. Have to serialize a larger than normal JSON blob? Well, it might reorder your timers. Whoops.