Introduction
At some point in time, you might have asked yourself "how exactly does the callback order of operations actually work?" Like if you have a piece of asynchronous code that calls 2 callback functions at the exact same time, how does the runtime pick what callback to actually execute first? Well this is because the node runtime follows an already predefined order of operations known as the Event Loop. In this article, we are going to understand all about what the event loop is and why it is a very important concept to know about. to better understand the execution process of you code. Let's get started.
What Is the Event Loop?
The Event Loop is a continuous running process that coordinates the operation of executions for asynchronous tasks in nodejs. The loop is only alive when your nodejs application is running.
When a nodejs project starts up, so does the event loop. You can think of it as a customer care representative. When the day starts, the customer care rep sits at their desk, waiting for a call to be made. When the call is made, they pick up their phone and listen to the request that is being made, after that, they execute whatever request was made. When that is done, they end the call and listen for a new incoming call.
This loop goes on and on till the day ends or in our case, till the application closes.
Now that we understand how the loop works, let's take a look at each of the process the loop goes through when running.
In each iteration of the loop, It encounters 6 different queues namely the:
Microtask Queue
Timer Queue
I/O Queue
Check Queue
Close Queue
Each queue, holds one or more callback functions that would be eventually executed on the call stack.
Now you might be thinking that these are just 5 queues mentioned, that is because the Microtask queue consists of two queues. The nextTick
queue and the promise
queue.
Let us take an in-depth look at each of the queues and how the event loop iterates through all of them.
Microtask Queue
Like mentioned previously, the microtask queue isn't just a single queue, but a queue that consist of 2 extra queues.
nextTick queue
Callbacks in this queue are registered as higher priority over all other callbacks in the event loop. This tells the loop to leave original flow of the iteration to execute whatever callback is present in this queue before going back to continue the execution of other callbacks from where it left off. You can register a call back in the nextTick queue using the process.nextTick(cb)
function, where cb
represents the callback to be registered.
promise queue
This queue is quite similar to the nextTick queue in the sense that the loop also break out from its initial flow to execute any available callback in the queue. A callback can be registered in this queue by using promise based asynchronous operations like promise.resolve()
or promise.reject()
, with its associated .then()
or .catch()
handler.
Timer Queue
To queue a callback into the timer queue, we make use of either the setTimeout(callback, ms)
or setInterval(callback, ms)
function.
Callbacks in the microtask queue are executed in-between the execution of a callbacks in the timer queue, meaning if along the lines of the callbacks in the timer queue being executed, a callback is then added to the microtask queue, the execution leaves the timer queue to go attend to the callbacks in the microtask queue before then going back to continue the execution of callbacks in the timer queue.
I/O Queue
This queue consist of callbacks from operations with the file system, like reading and writing to a file or making an API call. These callbacks typically involves the use of the async fs
and http
modules. while the event loop is in this queue, it comes across a poll phase. In this phase, the loop checks for new I/O events, like reading from a file or receiving data from a network socket. If there are no pending I/O events at the moment, the event loop will remain idle in this phase, or move on to execute callbacks in the next queue
Check Queue
This queue consists of any pending callbacks. The "check queue" is a separate queue that holds callbacks registered by setImmediate()
function. This is a function provided by nodejs that allows you to register a callback function to be executed asynchronously in the "Check" phase of the event loop. It is used to schedule callbacks that need to be executed right after the current phase, without waiting for other I/O events to be handled.
Close Queue
This queue is used to store any callbacks registered with resources that have been closed. When a resource in nodejs is closed by calling the close()
method, a close
event is emitted. When this event is emitted, any callback functions that were registered for that resource are added to the "close callback" queue and are to only be executed when the loop gets to the close queue section.
Summary of the entire iteration process
➡️ The loop first starts off running the micro tasks queue first, which consists of the nextTick queue which would be executed first above all(process.nextTick()
) and then the promise queue(promise.resolve()
, promise.reject()
). After every callback has been executed here,
➡️ All callbacks within the timer queue(setInterval()
, setTimeout()
) gets executed.
↩️ Then again, all callbacks in the microtask queue (if any) gets executed.
➡️ Next, all callbacks within the I/O queue(async fs
and http
module callbacks) are executed.
↩️ The loop then checks again to see if any new callback has been added to the microtask queue and if any, they get executed.
➡️ Next, all callbacks within the Check queue (setImmediate()
callbacks) are executed.
↩️ For the final time in the current iteration, all callbacks in the microtask queue (if any) gets executed.
➡️ Next, all callbacks in the Close queue(callbacks associated with the close
event of an async task) are executed.
🔁If there are more callbacks to be processed in any of the queues, the loop takes another iteration, and the same steps are repeated from top to bottom.
Conclusion
Now that we have understood perfectly well what the event loop is all about and the processes it encounters in each iteration, here are are a few things overall that you must have in mind:
🔴 The nextTick callbacks would always be executed before the promise callbacks.
🔴 nextTick and promise callbacks are always executed inbetween each queue and also inbetween each callback execution in the timer and check queues.
🔴 Using process.nextTick()
a lot in your code is highly discouraged, as it can starve the execution of the rest of the callbacks in the event loop.
🔴 In nodejs, all user written synchronous code, takes priority over any async code.
Well that is all for this article guys. I hope you have been able to learn one or two from all I've said so far. If you have any questions, please do not hesitate to ask in the comment section below, I would be more than happy to provide answers to them. Till then.
Top comments (0)