DEV Community

Rahul Vijayvergiya
Rahul Vijayvergiya

Posted on • Edited on • Originally published at rahulvijayvergiya.hashnode.dev

Event Loop and Concurrency in JavaScript

This post was initially published on my blog. Check out the original source using the link below:

Event Loop and Concurrency in JavaScript

This article explain how JavaScript handles asynchronous operations with the event loop, microtasks, and macrotasks.

favicon rahulvijayvergiya.hashnode.dev

What is the Event Loop?

Event loop is the core mechanism of javascript that allows it to perform non-blocking operations, even though it’s single-threaded. It continuously checks the call stack and the task queue (or message queue) to check what should be executed next.

Here's how the event loop operates:
1. Call Stack: JavaScript has a call stack that keeps track of function executions. When a function is called, it’s added to the stack. When it finishes executing, it’s removed from the stack.

2. Task Queue: When an asynchronous operation completes (e.g., an API call, setTimeout), its callback is placed in the task queue.

3. Event Loop: The event loop continuously monitors the call stack and the task queue. If the call stack is empty, the event loop will push the first task from the task queue into the call stack, allowing it to be executed.

Asynchronous Operations in JavaScript

JavaScript uses different types of asynchronous operations, including:

  • Timers (setTimeout, setInterval): These functions execute code after a specified delay.
  • Promises: Used for handling asynchronous operations, promises can be resolved or rejected and are managed by the microtask queue.
  • Event Listeners: Events such as clicks, key presses, and network requests trigger asynchronous callbacks.

Macrotasks vs. Microtasks in Event loop

To understand the event loop better, we need to distinguish between macrotasks and microtasks.

Macrotasks

Macrotasks include operations like setTimeout, setInterval, I/O, and event callbacks. When these operations are complete, their callbacks are pushed into the task queue, waiting for the event loop to pick them up.

Microtasks

Microtasks, on the other hand, are primarily related to promise resolutions and MutationObserver. When a microtask is queued, it is processed after the currently executing script and before any macrotasks. This gives microtasks higher priority over macrotasks.

How the Event Loop Handles Tasks

Let’s break down the event loop with an example:

console.log('Start');

setTimeout(() => {
  console.log('Timeout');
}, 0);

Promise.resolve().then(() => {
  console.log('Promise');
});

console.log('End');
Enter fullscreen mode Exit fullscreen mode

Output:

Start
End
Promise
Timeout
Enter fullscreen mode Exit fullscreen mode

Here’s what happens:

1) The synchronous code is executed first:

  • console.log('Start') is executed and logged.
  • setTimeout is encountered, and its callback is queued as a macrotask.
  • Promise.resolve().then(...) is encountered, and its callback is queued as a microtask.
  • console.log('End') is executed and logged.

2) The call stack is now empty, so the event loop checks the microtask queue:

  • The promise’s .then() callback is executed, logging Promise.

3) Only after the microtask queue is empty does the event loop move to the macrotask queue:

  • The setTimeout callback is executed, logging Timeout.

Understanding Event Loop as a Restaurant

To make the concept of the event loop more easy to understand, let’s compare it to how a restaurant kitchen operates.

1. The Call Stack as the Chef: Imagine the chef is the call stack, responsible for cooking one dish at a time. When an order comes in, the chef starts preparing it. They can only work on one dish at a time, so if a new order comes in while they’re busy, it has to wait until the current dish is finished.

2. The Task Queue as the Wait Staff: The task queue is like the wait staff, who bring orders to the chef. They keep track of the orders waiting to be prepared. When the chef finishes a dish, the next order is passed to them to start cooking.

3. Microtasks as Quick Fixes: Sometimes, the chef might need to do something really quick, like adding a garnish or tasting a sauce. These are like microtasks—small, quick jobs that get done before the chef moves on to the next big order.

4. Macrotasks as Full Orders: Full meal preparations, like cooking a steak or making a salad, represent macrotasks. These take more time and are handled one after the other.

5. Event Loop as the Kitchen Manager: The event loop is like the kitchen manager who oversees everything, ensuring the chef stays busy but not overwhelmed. They make sure the chef handles quick fixes (microtasks) before starting the next full order (macrotask).

Common Issues

Understanding the event loop, microtasks, and macrotasks helps avoid common pitfalls in JavaScript, such as:

1) Starvation of macrotasks: when macrotasks (like setTimeout, setInterval, and event callbacks) are delayed or don't get a chance to run because the event loop is constantly busy handling microtasks (like promise callbacks). In simpler terms, if your code keeps adding more and more microtasks, they can pile up and get processed continuously, leaving no time for the macrotasks to execute. This can cause certain actions, like a setTimeout callback, to be delayed or not run as expected, which can make your application feel slow or unresponsive.

2) UI responsiveness: Since microtasks are executed before the rendering phase, heavy usage of microtasks can delay the UI update.

Conclusion
The event loop, with its handling of microtasks and macrotasks gives JavaScript its concurrency capabilities. By managing the execution of async tasks efficiently, JavaScript ensures that web applications remain responsive and can handle multiple operations without blocking the main thread.

References

Top comments (0)