Demystifying the JavaScript Event Loop: A Step-by-Step Explanation 🔁

Demystifying the JavaScript Event Loop: A Step-by-Step Explanation 🔁

The JavaScript event loop is a vital concept to understand when working with asynchronous operations. In this blog post, we will dive into an example code snippet to study how the event loop processes different tasks in JavaScript. By dissecting each function and its related asynchronous operations, we'll gain insight into the order of execution and the role of the event loop.

What is the Event loop? 🤔

The event loop is a mechanism in JavaScript that allows the execution of several tasks concurrently. JavaScript is single-threaded, meaning it can only perform a single operation at a time. However, the event loop ensures that even though JavaScript executes code synchronously, it can also handle asynchronous tasks efficiently.

Components of the Event Loop:🤘

To understand how the event loop works, let's discuss its key components:

Call Stack:

The call stack is a data structure that keeps track of function calls in the execution context. Whenever a function is called, it is added to the top of the call stack. The JavaScript engine runs the functions sequentially from the top of the call stack. When a function completes execution, it is removed from the stack, allowing the next function in line to be executed.

Heap:

The heap is the memory space where objects and variables are stored.

Task Queue:

The task queue (also known as the macro task queue), is where JavaScript stores the tasks that are delegated to the browser or the operating system, such as timers, events, and network requests. When these tasks are finished, their corresponding callback functions are put into the task queue, waiting to be executed. JavaScript will only perform these tasks when the call stack is empty, and it will always take the oldest task from the queue (the one that was added first).

Job Queue:

The Job Queue also known as the microtask queue, is where JavaScript stores the tasks that are related to promises, such as .then(), .catch(), and .finally() callbacks. When a promise is settled (either resolved or rejected), its corresponding callback function is put into the microtask queue, ready to be executed. JavaScript will only run these tasks when both the call stack and the task queue are empty, and it will execute all of them before moving to the next task in the task queue. Therefore, microtasks have higher importance than regular tasks.

Priority: Job Queue >> Task Queue

Web APIs:

Web APIs are provided by the browser or the JavaScript runtime environment, allowing developers to perform different tasks asynchronously. Examples include setTimeout, XMLHttpRequest, and the DOM (Document Object Model) API. When an asynchronous task is initiated, it is sent to the Web API, and once finished, the corresponding callback is placed in the event queue.

Points to be noted:

➡️The event loop works by continuously alternating between the following two steps:

  1. Checking the call stack: If the call stack is not empty, the event loop will continue executing the next function on the call stack.

  2. Checking the event queue: If the event queue is not empty, the event loop will remove the first event from the event queue and execute it.

The event loop will continue to alternate between these two steps until the call stack is empty and the event queue is empty.

How does the event loop handle asynchronous code?

JavaScript code can be asynchronous, which means that it does not halt the execution of other code. Asynchronous code is often used to handle events, such as user input and network requests.

The event loop handles asynchronous code by using callbacks. A callback is a function that is passed as an argument to another function. When the other function is finished executing, it will call the callback function.

When an asynchronous function is called, it gets placed on the call stack. However, the asynchronous function will not start executing until the event loop is ready to handle it. When the event loop is ready to handle the asynchronous function, it will call the callback function that was passed to the asynchronous function.

What are the benefits of the JavaScript event loop?➕➕

The JavaScript event loop has several uses, including:

Concurrency: The event loop allows JavaScript code to run simultaneously with other events, such as user input and network requests. This can improve the responsiveness of JavaScript apps.

Simplicity: The event loop is a simple and elegant way to handle the execution of JavaScript code. This makes it easy to write and understand JavaScript code.

Efficiency: The event loop is an efficient way to run JavaScript code. This is because the event loop only runs JavaScript code when it is needed.

Event loop process🧐

Now that we understand the components, let's see how the event loop coordinates these components to execute JavaScript code efficiently:

  1. The JavaScript engine starts by executing the initial synchronous code, adding function calls to the call stack.

  2. If an asynchronous task, such as a timer or a network request, occurs, it is handed off to the Web API. The JavaScript engine continues to execute the next code in line.

  3. Once the asynchronous task is finished, the associated callback is placed in the event queue.

  4. The event loop constantly checks the call stack and the event queue.

  5. If the call stack is empty, it picks the oldest callback from the event queue and pushes it onto the call stack for processing.

  6. The callback is performed, and any functions called within it are added to the call stack.

  7. The process repeats until the event queue is empty.

Example 😁

function one() {
    console.log("one");
}

function second() {
    setTimeout(() => {
        console.log("two");
    }, 0);
}

function three() {
    Promise.resolve(1)
        .then(() => {
            console.log("three");
        });
}

function four() {
    console.log("four");
}

Output:

one
four
three
two

Explanation:

  1. one() is called, and "one" is logged. This is added to the stack and executed immediately.

  2. second() is called. setTimeout() is used which adds a callback function to the web API's timer queue. The time is set to 0ms, meaning it will be executed as soon as possible. However, it is not added to the stack yet. "two" is not logged yet.

  3. three() is called. Promise.resolve() returns a resolved promise, and the callback is added to the microtask queue. "three" is not logged yet.

  4. four() is called and "four" is logged. This is added to the stack and executed immediately.

  5. Now the stack is empty, so the event loop checks if there is anything in the microtask queue. The callback from the resolved promise in three() is there, so "three" is logged.

  6. The microtask queue is now empty. The event loop checks the timer queue and sees the callback from setTimeout() in second(). "two" is logged.

  7. All queues are empty. The event loop waits for more messages to arrive.

Note: The event loop executes the code asynchronously, giving priority to the microtask queue before the timer queue.

Job Queue >>> Task Queue...

  • Go through my visualized drawings for a step-by-step explanation that provides intuitive understanding. (how does this work?)

Conclusion:

In the example code, we saw how synchronous and asynchronous tasks interact within the event loop. Tasks such as setTimeout and Promise are scheduled for execution in the future, and their associated callbacks are placed in the event queue. The event loop constantly checks the call stack and the event queue, ensuring that tasks are executed in the expected order.

By grasping the complex concepts of the event loop, developers can write efficient and responsive JavaScript code that uses asynchronous operations effectively.