by Rajika Imal

The differences between JavaScript’s asynchronous API timers

Photo by Szűcs László on Unsplash

JavaScript is a single-threaded language, which makes use of asynchronous constructs to handle tasks concurrently. Interestingly, it handles concurrent tasks efficiently with a different approach compared to traditional languages likes Java and C#.

Event loop

Whether it’s a browser environment or Node.js, JavaScript is asynchronous due to the fact that it’s using the event loop. In the Node environment, it’s implemented using a libuv library. Originally libuv was developed as a wrapper to libev. In Node version 0.9.0, the dependency of libev was removed.

Phases in event loops

   ┌───────────────────────────┐┌─>│           timers          ││  └─────────────┬─────────────┘│  ┌─────────────┴─────────────┐│  │     pending callbacks     ││  └─────────────┬─────────────┘│  ┌─────────────┴─────────────┐│  │       idle, prepare       ││  └─────────────┬─────────────┘      ┌───────────────┐│  ┌─────────────┴─────────────┐      │   incoming:   ││  │           poll            │<─────┤  connections, ││  └─────────────┬─────────────┘      │   data, etc.  ││  ┌─────────────┴─────────────┐      └───────────────┘│  │           check           ││  └─────────────┬─────────────┘│  ┌─────────────┴─────────────┐└──┤      close callbacks      │   └───────────────────────────┘
source: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/

Event loops can be divided into a few phases as illustrated above. Each phase will be executed in each iteration. One such iteration is called a tick in the event loop. Every phase has a first in first out queue (FIFO) which will register different tasks. To understand how setTimeout, setImmediate and nextTick work, we'll go through the relevant important phases.

Timers phase

Callbacks registered by setTimeout and setInterval will be executed in this phase. It’s important to notice that callbacks will not be executed immediately but rather after a certain threshold of the time expiring.

Check phase

If the poll phase which handles I/O callbacks becomes idle or the maximum number of executions exceed it will move to check phase, where it’ll execute callbacks registered by setImmediate.

Microtask queue and macrotask queue

These two queues are important to understand the order of tasks executed through different APIs. Macrotasks are executed in each of the phases shown in the diagram above.

setImmediate is part of the macrotask queue. Microtasks will be executed until the queue is empty before moving on to the next iteration or the tick of the event loop.

process.nextTick callbacks will be registered in the microtask queue and they will be executed until it is empty. Therefore, having recursive calls in the process.nextTick can starve the event loop, prevent it from going to the next tick. Macro tasks won’t starve the event loop as it will move on the next tick once the maximum number of executions is reached.

Let’s look at a few examples to see how each of the APIs behave in the real world to get a better understanding.

In the rest of the examples shown in this article, Node.js will be used as the execution environment.

setTimeout vs setImmediate

Notice that the calls aren’t within an I/O cycle. Because of this fact, the execution will depend on the performance of the CPU. Therefore logs will be printed out randomly in this case.

In this example, they are within an I/O cycle. setImmediate callback will get executed every time since the macrotask queue (check phase) will be executed following the tick. setTimeout will be called in the timers phase once the threshold gets exceeded.

setImmediate vs process.nextTick

nextTick is part of the microtask queue, and it will get executed before event loop moves on to the next tick. Following nextTick in the next tick setImmediate will fire off its callback in the macrotask queue in the check phase.

nextTick executes the recursive function which will get executed until it enters the base condition (if num > 5). Only after the execution of nextTick, setImmediate will fire its callback. Continuous recursive behavior is due to nextTick being a part of the microtask queue which doesn’t allow the event loop to proceed to the next tick.

setImmediate vs setTimeout vs process.nextTick

As expected nextTick gets called first, followed by setImmediate and setTimeout. It’s important to note that the functions are called in an I/O cycle. If they are not within an I/O cycle output will be different and will be dependant on the process performance.

Follow up resources

Concurrency model and Event Loop
JavaScript has a concurrency model based on an "event loop". This model is quite different from models in other…developer.mozilla.orgThe Node.js Event Loop, Timers, and process.nextTick() | Node.js
Edit on GitHub The event loop is what allows Node.js to perform non-blocking I/O operations - despite the fact that…nodejs.orgTasks, microtasks, queues and schedules
When I told my colleague Matt Gaunt I was thinking of writing a piece on microtask queueing and execution within the…jakearchibald.com