What is an Event Loop in JavaScript ?

What is an Event Loop in JavaScript ?

An Event Loop is a fundamental concept in JavaScript and I feel every budding JavaScript developer must try to understand it. In this article, I have tried to explain Event Loop in the simplest language.

Let's begin!

We know that JavaScript is a Single Threaded language, which means that it can execute only 1 task at a time. But if one request takes more than 2 minutes to execute, will the user be kept waiting forever?

This is certainly not an ideal situation and thankfully JavaScript provides us with Event Loop which gives us the superpower 💥 to process multiple requests asynchronously.

How does JavaScript Engine execute the code using Call Stack?

There is a Call Stack that is present inside the JavaScript engine and all the code is executed inside this single Call Stack.

Let us consider a sample program

function logger(){
    console.log("Inside logger");
}
logger();
console.log("End");

For the above program, a Call Stack is created and a Global Execution Context(GEC) is pushed into the call stack. The first line has the function definition of logger. So memory is allocated for the function. After that logger function is invoked. So an Execution Context is created for logger and pushed into the Call Stack. The code inside the function logger will be executed and "Inside logger" will be logged into the console. Then the execution context created for this function will be popped out. Then it moves on to the next line and logs "End" into the console.

Drawing-2.sketchpad.png

After this, there is no more code to be executed. So GEC will also be popped out of the Call Stack. And we are done with running our code. This is how JavaScript executes our code.

The main job of Call Stack is to execute whatever is pushed into it. It does not wait for anything else. But what if we need to wait for some time? What if we need to run few lines of code after a few seconds?

We cannot do that with Call Stack because as we saw, whatever is pushed into the Call Stack is quickly executed. The Call Stack does not have a timer. So to achieve this, we need access to some other powers. So this can be achieved using Web APIs.

Web APIs in JS

The Web APIs have several APIs such as setTimeout, DOM APIs, fetch API, localStorage, console, location, etc.. We get access to all these APIs through the Global Object which we often use using the window object.

So if we have to access any of the APIs such as setTimeout we have to do it using window.setTimeout. But when we usually write code, we don't write window.setTimeout right? This is because window is a global object and setTimeout is present in the global scope, we don't have to necessarily use window.setTimeout to use the API.

How setTimeout works?

Let us take the sample code below

console.log("Start");

setTimeout(function cb(){
    console.log("Callback");
}, 5000);

console.log("End")

Output:

Start
End
Callback

The consle.log() accesses the console API from the Web APIs and outputs the message into the console.

When we encounter setTImeout, it calls the Web API setTimeout and it gives us access to the timer⌛. The setTimeout takes a callback function and a delay, and the callback function is registered inside the Web API environment. The timer is started and then the program proceeds with the remaining lines of code. After each and every line of the code is executed, the GEC is also popped out of the stack. But there is a callback function waiting to be executed as soon as the timer expires. After the timer expires, we need to execute the callback function. But we know that every code in JavaScript is executed inside call stack, we need to somehow push the callback function into the call stack. But how do we do it?

Drawing-1.sketchpad.png This is where the hero of this article, The Event Loop 😎 comes into the picture along with the Callback Queue.

Event Loop

When the timer expires, the callback function is pushed inside the callback queue. And the job of the Event Loop is to check if the call stack is empty and if there are any functions in the callback queue and push the functions from the callback queue into the call stack so that it can be executed.

Drawing-1.sketchpad (1).png

Working of Event Listeners in JS

document.getElementById("btn")
.addEventListener("click", function cb() {
    console.log("Callback");
});

Event listeners are provided by the DOM API. When an event listener is add to any element, the callback function is registered in the Web API environment and an event is attached to it. The registered callback function sits in the Web API environment until it is explicitly removed or the browser is closed. When the event occurs, the callback function is pushed into the callback queue to be executed.

Why do we need Event Loop❓

So you might wonder why an Event loop is required and why can't the callback function directly be pushed into the Call Stack. The reason why we require Event Loop is because there might be several event listeners , timers, etc.. and JS has only one Call Stack to execute these callback functions. So every callback needs to be queued into the callback queue so they can be executed one after the other.

Working of Fetch API

Fetch API does not work the same way as setTimeout or event listeners.

setTimeout(function cbT() {
    console.log("CB setTimeout");
}, 5000);
fetch("https://api.netfix.com")
.then(function cbF(){
    console.log("CB Netflix");
});

The setTimeout's callback function cbT is registered in the Web APIs environment and the timer of 5000ms will also be started. Then in the next line, the Fetch API is used to fetch data from Netflix. Also the callback function of fetch cbF is registered in the Web APIs environment. The cbT function is waiting for the timer to expire and the cbF function is waiting for data to be received from the servers. Suppose the Netflix servers are very fast and the data is received in 50ms, and the callback function cbF is ready to be executed. But what do you think will happen? Will it be moved to the callback queue? No! The callback function will be moved into the Microtask Queue.

What is a Microtask Queue❓

Microtask Queue is exactly similar to the Callback Queue but it has higher priority. Whatever function are present in the microtask queue will be executed first and the functions in callback queue will be executed later. Let us consider after the fetch is done, there are millions of lines of code which take a while to be executed. During this time, the 5000ms timer of setTimeout also expires and the callback function is pushed into callback queue. Now the cbF function is waiting inside the microtask queue and cbT is waiting inside callback queue. After the whole code is executed, the event loop first pushes cbF into the call stack to be executed. After execution of cbF, cbT is pushed inside the call stack to be executed and the program is done. So this is how the call stack, event loop, callback queue and microtask queue work together.

Drawing-1.sketchpad (2).png

What are Microtasks in JS❓

All the callback functions which comes through promises will go into the microtask queue. Also the mutation observers which keeps on checking whether there is any mutation in the DOM tree so that it can execute a callback function will also go into the microtask queue. All the other callback functions will go inside the callback queue(also known as task queue).

Starvation of functions in callback queue:

If there are several callback functions to be executed inside the microtask queue, there is a possibility that the callback functions inside the callback queue does not get a chance to execute. This is known as Starvation of Callback Queue.

Thank you for reading so far. 🙏🏻Let me know in the comments👇 if you understood this amazing concept of Event Loops. Do like and share if you found this helpful.

References:

Akshay Saini - Youtube

MDN Docs