setImmediate() vs nextTick() vs setTimeout(fn,0) – in depth explanation

Few days back, I was guiding some new node.js developers on making asynchronous stuffs. We were discussing about async apis of node js. I wanted to provide them with some references and googled for few; but surprisingly, majority of the articles out there in the internet about setImmediate() or process.nextTick() was containing insufficient or misleading information. And going through official documents of Node may not really be feasible for non-advanced developers. Hence I decided to come up with this article.

Know the misconceptions first

Before I start describing anything, I would like to clear some of the misconceptions of the other articles, covering this topic. If you are not misled yet, you can skip this section.

setImmediate() runs before setTimeout(fn, 0)

This is one of the most common misconceptions. I will discuss about the right concepts later on this article, but below is a proof of this statement being false.

If the statement above was true; running the above code would have given an output where SETIMMEDIATE would have been printed always before SETTIMEOUT. However in reality, the output of the above is not predictable. If you run node index.js multiple times, you will find multiple orders.

setImmediate() puts the callback ahead of the job queue

If the statement above was true; it would have produced the following output.

But the actual output is like the following; irrespective of how many times you run it.

nextTick() triggers the callback on next tick (iteration)

Actually both process.nextTick() and setImmediate() was named wrongly. If we swap the names of those then the names will match the functionality. However as in JavaScript, they do not deprecate/change apis, so the named continued as wrong.
In terms of functionality, process.nextTick() is actually the way to invoke a callback immediately. Callback in setImmediate() will be triggered during/next iteration.

How node.js event loop works

The only way to understand the workflow and the differences between these three functions; you must understand the functioning of the event loop. Hope you already know that event loop handles all async callbacks, but here we will discuss how it does so.

event-loop

Each rectangular box in the diagram represent a phase and event loops iterates on those again and again, starting from timers to close callbacks. There is also a nextTickQueue in the middle, however it’s not a part of the event loop itself. Each phase has a queue attached to it. When event loop enters in a particular phase, its target is to execute the callbacks/tasks in those queues. A little description about the phases are as below.

Timer: It handles the callbacks assigned by setTimeout & setInterval after the given time threshold is completed.
I/O callbacks: Handles all callbacks except the ones set by setTimeout, setInterval & setImmediate. It also does not have any close callbacks.
Idle, prepare: Used internally.
Pole: Retrieve new I/O events. This is which makes node a cool dude.
Check: Here the callbacks of setImmediate() is handled.
Close callbacks: Handles close connection callbacks etc. (eg: socket connection close)
nextTickQueue: Holds the callbacks of process.nextTick(); but not a part of the event loop.

How event loop propagates

It enters the Timer phase & checks if anything (callback) is there in the timer queue. If there are some, it starts executing one after another till either the queue is empty or the maximum allowed callback execution is completed.

After Timer it moves to the I/O callback phase where it again find the queue associated with it for i/o operations. It followed the similar approach as timer and after task done moves to the next phase.

Idle phase is used by node internally; for preparation etc. After that, the event loop enters the Poll phase where it handles events. If there is no event to be handled then also the event loops waits a bit in the poll phase for new i/o events. Nothing in the event loops works when poll phase is in waiting or sleep mode. However if there are some scripts assigned by setImmediate the event loop will end the poll phase and continue to the Check phase to execute those scheduled scripts.

After Check it will try executing anything in Close callbacks and after that goes back to Timer for the next iteration or tick.

Now about nextTickQueue. Any callbacks assigned by process.nextTick() is queued in the nextTickQueue and the event loop executes them one after another another, till the entire queue is drained out; after completing the ongoing operation; irrespective of which phase it is in.

This concludes the event loop description and now we may try to understand the three apis mentioned in the title of this article.

setImmediate()

So first of all, by the workflow of event loop, now we can say setImmediate() is not exactly immediate, but the queue containing the callbacks of this, will be executed once in every iteration (when event loop is in Check phase).

So, the example in previous section; things were non-deterministic, because it depends on the performance of the process. However if we move the piece of code in an I/O callback; we can guarantee that the callback of setImmediate will be called before setTimeout, irrespective of anything else.

setTimeout(fn,0)

This also invokes the callback, but will not be executed till the event loop enters the Timer phase. So any setTimeout(fn, 0) along with setImmediate() in the Close callback phase will guarantee the execution of setTimeout 0 before the setImmediate. And accordingly, keeping the phase diagram of event loop in your mind, you can easily determine whether it’s setTimeout(fn, 0) or setImmediate() which will be called at the earliest.

process.nextTick()

process.nextTick() is actually interesting cause irrespective of the current phase of the event loop, this will start being executed right after the current operation is completed. So if the event loop is in Timer and there were 5 callbacks in the timer queue already; and event loop is busy executing the third one. By that time if few process.nextTick() callbacks are pushed to nextTickQueue, the event loop will execute all of them synchronously after completing the current callback execution (which is 3rd one) and will resume the Timer callback execution again from the 4th callback.

About This Author

Hello! I am Paul Shan, a JavaScript Expert, Full Stack and DevOps Engineer cum Consultant based out of Bengaluru, India.

  • WWII

    This is by far the best illustration of the topic..

    • Ano Nym

      Yeah, I agree!

    • Paul Shan

      Thanks 🙂

  • Павел Ашифин

    Very good explanation!
    Thanks a lot!