The Last Event Loop Guide you will Ever Need

Yes, I said it. This is it.

💡 Read this Guide end to end to understand exactly how the Dreaded Event Loop in Javascript or NodeJS works.

Step 1️⃣ : Synchronous and Asynchronous

As a developer, you need to understand every code you write needs to be executed either Synchronously or Asynchronously.

Synchronous

Synchronous execution is when your program is executed line by line.

Take this code snippet for example :

console.log("Entry 👇🏻");

function add(firstNumber, secondNumber){
   return firstNumber + secondNumber;
}
console.log("Function Called : ",add(2,5));
console.log("Below function 🧐");

Enter fullscreen mode Exit fullscreen mode

The output is :

Entry 👇🏻
Function Called : 7
Below function 🧐
Enter fullscreen mode Exit fullscreen mode

Pretty simple right? The code follows a top to bottom approach of execution, with the function returning the sum immediately.

Let's take a different scenario.

Asynchronous

Asynchronous code is simply put to work on a separate context for execution. This doesn't mean that it is not being executed.

💡 Asynchrony, in computer programming, refers to the occurrence of events independent of the main program flow and ways to deal with such events.

This essentially means tasks that can be delegated away from the main thread of execution.

Let's take an example of reading data from a file using NodeJS.

const fs = require("fs");
console.log("Entry 👇🏻");

fs.readFile('sample.txt', 'utf8', (err, data) => {
    if (err) {
      // Handle error if file reading fails
      console.log("Error reading file");
    } else {
      // Invoke callback with file content
      console.log("File reading done. Contents 👇🏻\n",data);
    }
 });

console.log("Below function 🧐");
Enter fullscreen mode Exit fullscreen mode

Can you guess the Output?

Let me help you.

Entry 👇🏻
Below function 🧐

File reading done. Contents 👇🏻
Hello, I am the content in sample.txt file!!

Enter fullscreen mode Exit fullscreen mode

Can you spot the difference?
This is what asynchronous programming helps us do.

File reading is a CPU heavy process(if for small files, this takes more time than a simple program execution).

But this is not the reason that the contents are displayed after the console.log after the file reading method.

This happens because NodeJS defines fs.readFile as an asynchronous task. And NodeJS gives priority to Synchronous tasks over Asynchronous tasks.

Step 2️⃣ : Understanding the NodeJS environment

NodeJS is a Library built to run Javascript outside of the browser.

We will not get into the details of why and how this is done.

But let's get into how NodeJS handles the execution of a program.

NodeJS internally has 3 main parts that handle the execution of a Javascript program.

For the above 2 programs, let's see individually how NodeJS handles execution internally using the images below(for Better Understanding) :

First Program :

nodejs internal working

To understand the working of the second program, read the section below with the simulation

Step 3️⃣ : Understanding the Thread Pool and Callback Queue

For all the non-blocking code, it passed to the Thread Pool implemented using LibUV library.

The threads are tasked with executing the Asynchronous tasks outside of the main thread. This helps NodeJS execute so many network calls at once, even though Javascript is a Single-Threaded Language.

  • The Threads process the tasks and pass the callbacks into the callback queue.

  • The Callbacks are queued according to a certain priority, which we will discuss in the below sections.

💡 By default there are 4 working threads in Libuv. But you can increase them upto : MAXIMUM NO. OF CPU CORE AVAILABLE which you can get using the code below :

const max = os.cpus().length;
Enter fullscreen mode Exit fullscreen mode
  • Callbacks are picked up by the Event Loop and pushed onto to the Call Stack of Javascript, ONLY WHEN THE CALLSTACK IS EMPTY.

asynchronous code execution in NodeJS

Step 4️⃣ : The Event Loop

The Event Loop is nothing but a scheduling algorithm.

Yes, it is that simple.

A scheduling algorithm that defines which callbacks are to be prioritized over the others.

But before understanding the order, understand that there are 6 types of callbacks, based on the calling instance.

6 Types of callbacks :

  • Timer Callbacks : You might have used the setTimeout and the setInterval methods.

  • I/O Callbacks : Input/Output operations are the File reading and Network based operations.

  • Check Callbacks : There is a NodeJS specific function : setImmediate used to execute a Callback as soon as the current event loop finishes.
    This is basically like the Hooks in ReactJS, which helps us hook into the lifecycle of NodeJS execution.

  • Close Callbacks : For the I/O operations, we have event listeners to listen to the closing event. Once the event is fired, these callbacks are defined to be executed.

  • Promise Callbacks : When promises are invoked, we supply callbacks to be called based on the result of the promise.

  • Next Tick Callbacks : Next Tick is a function native to NodeJS modules (process.nextTick()) used by developers in real-time applications every day to defer the execution of a function until the next Event Loop Iteration.

The Event Loop schedules the callbacks, which is visualized in the following diagram :

Event Loop Visualized

I know it might be overwhelming, but is simple.

The steps are as follows :

  1. Check if callstack is Empty, then Proceed as follows.

  2. Callbacks in the Microtask Queue which is divided in 2 parts :
    i) Execute the nextTick callbacks.
    ii) Execute the Promise callbacks.

  3. Callbacks in the Timer Queue, but there is a catch here.

    🚨 After every callback in the Timer Queue is executed, REPEAT STEP 2(Microtask Queue) before moving to next callback in Timer Queue.

  4. Execute callbacks in the I/O Queue.

  5. REPEAT STEP 2 (Microtask Queue)

  6. Execute Callbacks in the Check Queue i.e. the setImmediate callbacks, but there is a catch again.

    🚨 After every callback in the Timer Queue is executed, REPEAT STEP 2(Microtask Queue) before moving to next callback in Timer Queue.

  7. Execute all the callbacks in the Close Queue

  8. REPEAT STEP 2 (Microtask Queue)

  9. Check if there process needs to repeated. If NOT 👉🏻 then EXIT.

exhausted

This is the Event Loop steps simplified.

But we are Developers. Of-course we need examples.

So here are a few links which you can use for examples :