Unlocking the Magic: A Journey Through JavaScript's Event Loop

Unlocking the Magic: A Journey Through JavaScript's Event Loop

Javascript is a Synchronous Single Thread Language. It has one call stack and everything is executed inside the call stack. The call stack can perform only one task at a time.

Whenever any Javascript Program runs a Global Execution Context (GEC) is created. GEC is then pushed into the call stack

1. function Print() {
2.    console.log('Event Loop');
3. }
4. Print();

The whole code is running line by line in the Global Execution Context. Print function is defined and some memory is allocated to it. The print function gets invoked on the fourth line and an execution context is created and pushed into the call stack for the print function to execute the code of the Print Function.

After the execution of the Print function, the execution context of the Print function will get Poped and after executing the entire code the global execution context will also get Poped off. The main job of the call stack is to quickly execute whatever comes inside it. The call stack will not wait for anything to execute the code.

But what if we want our javascript code to wait for a few seconds and then execute the code?

By observing the current scenario it won't be possible. Because the call stack does not have a timer.

To keep track of time and execute our code after some delay we need access to the timer. Access to the timer will be provided by our Browsers. The browser has local storage for data storage, it has a timer and also it allows us to communicate with the external world. Displaying the data and many more things like Gps, Bluetooth etc.

Web API's

The Javascript code which is running in the call stack of the JS Engine can access the functionality of the Browser using Web APIs.

The Browser gave access to all these functionalities inside the Javascript Engine to use all the functionality of the Timer, Display the data on the screen, and Communicate with the external world, Storage etc.

SetTimeout is used to access the Timer Functionality Of Browser.

DOM APIs are used to access the DOM Tree.

Similarly, Fetch is used to make the connection to the external world.

Global Object Window.

You can access all these Web APIs in the Javascript Call Stack by using the Global Object Window.

Since Window is the global object and the Web APIs are present in the global access you can use it with or without using the Window keyword

// Since window is the global object.
window.setTimeout(() => {
    console.log('Print This After 5 Second With Using Window keyword');
},5000);

setTimeout(() => {
    console.log('Print This After 5 Second Without Using window keyword');
},5000)
// Guess the Output
console.log('Start');
setTimeout(function cb(){
    console.log('Callback');
},0);
let count = 0;
for(let i = 0;i < 100000; i++) {
    for(let j = 0;j < 50000; j++) {
        count += 1;
    }
}
console.log(count);
console.log('End');

// Output

Start-------------------------------> Printed at the very begining.
5000000000--------------------------> Printed after 20 Seconds
End---------------------------------> Printed just after printing 5000000000
Callback----------------------------> Printed at the end

Since the Loop is running exactly 5 BILLION TIMES and the 5000000000 is printed after approximately 20 Seconds you might be thinking that the Callback should be printed before 5000000000 or after 5000000000 but it shouldn't be after End.

Let me tell you, my dear friend here's a catch.

And there are some Jargons like EVENT LOOP, CALLBACK QUEUES, MICROTASK QUEUES.

Whenever we run our Javascript Code a Global Execution Context is created. The (GEC) is pushed into the call stack and the code starts executing line by line.

First, it will print Start in the console.

Second, The setTimeout will go and call the Web API to access the Timer feature of the Browser and it takes one callback function & delay. It registers a callback in the Web API Environment and the delay will be used by the Timer feature of the Browser.

As soon as the timer is expired our callback function needs to be executed. For that, it needs to go inside the call stack. But it cannot go directly into the call stack**.** It will go to the call stack through the CALLBACK QUEUE. So as soon as the timer expired it will move into the CALLBACK QUEUE.

And Finally, the wait is over the Hero of the movie is here THE EVENT LOOP.

The job of the EVENT LOOP is to check the CALLBACK QUEUE and put the function of CALLBACK QUEUE into the Call Stack. The call stack will quickly execute the callback function.

The Event Loop is constantly checking whether is there any function in the Callback Queue and whether the Call stack is empty or not. After the execution of code in the Global Execution Context an Execution Context of the Callback function is created and it is pushed into the Call Stack of the Javascript Engine. The Javascript Engine will quickly execute the call-back function.

Since the execution of the code of Global Execution Context is completed it will be popped off from the call stack.

This will print the 5000000000 and End in the console.

Now the execution context of the Callback function is pushed into the call stack and it started executing line by line.

This will print the word Callback in the console.

Do you know what are promises?

As per the MDN docs,

A promise is an object which represents the eventual completion (or failure) of an asynchronous operation and its resulting value.

It is used to handle async operations in Javascript.

const promise = fetch('https://api.github.com/users/Vikram125609');

Whenever the above line is executed it will return a promise.

The promise is nothing but an empty object like this {data: undefined} The data will hold the value which is returned by the fetch

But after some time it will fill the object automatically {data: Response}

To access the data returned by API we need to attach a callback on the Promise object using then function

const result = fetch('https://api.github.com/users/Vikram125609');
console.log(result);
result.then((data) => {
    console.log(data);
})
let count = 0;
for (let i = 0; i < 1000; i++) {
    for (let j = 0; j < 500; j++) {
        count += 1;
    }
}
console.log(count);
console.log(result);

To get data from the external world we need to use the WebAPI fetch returns a promise and we have to pass the callback function which will be executed when the promise is resolved.

// Guess The Output
setTimeout(function cbTimeout() {
    console.log('Timeout Callback');
}, 1);
fetch('https://api.github.com/users/Vikram125609').then(function cbFetch(data) {
    console.log(data);
});
let count = 0;
const time1 = new Date();
for (let i = 0; i < 100000; i++) {
    for (let j = 0; j < 50000; j++) {
        count += 1;
    }
}
const time2 = new Date();
console.log(time2 - time1);

Since Now you have the Knowledge Of Global Execution Context. I think you had to Guess the Output like this.

  1. First It Will Print The Time.

  2. Since there is a delay of 1 millisecond to set timeout it will print either the data from the fetch WebAPI or it will print Callback Timeout

Similar to Callback Queue We have one more very important thing that is Microtask Queue

The time taken by the GitHub API is 256ms

As soon as the Timer get expired and the Data is fetched from the API.

The cbFetch will be pushed into the Microtask Queue and the cbTimeout will be pushed into the Callback Queue

As soon as the code in the Global Execution Context is executed completely it will be poped from the call stack

And you super hero Event Loop is constantly checking weather the call stack is empty or not and do we have any pending task in the Callback of Microtask Queue.

The callback which are generated from the Promises are passed into the Microtask Queue and other callback of some async are passed into Callback Queue

Priority of Microtask Queue > Priority of Callback Queue.

At the end the cbTimeout will be poped out from the Call Stack

Suppose you have 10+ task pending in the Microtask Queue and 1 task pending in the callback queue. All the task in the Microtask Queue will be executed first rather Callback Queue.