How asynchronous code is executed in JavaScript

BhupendraBhupendra
3 min read

In JavaScript, asynchronous code is handled using the event loop, which allows non-blocking execution. JavaScript is single-threaded, meaning it can only perform one task at a time. However, it can handle asynchronous tasks efficiently without blocking the main thread by leveraging callback queues and promises.

1. Call Stack

  • JavaScript has a call stack where functions are added and executed in a Last In, First Out (LIFO) manner.

  • When a function is called, it is pushed onto the stack. Once it completes, it is popped off.

2. Web APIs (Browser or Node.js APIs)

  • When JavaScript encounters asynchronous operations like setTimeout, fetch, or Event Listeners, it offloads these to the browser (or Node.js) APIs. These operations run in the background, freeing up the main thread.

3. Callback Queue

  • Once an asynchronous task completes, its callback is added to the callback queue (also called the task queue). This queue holds all callbacks that are waiting to be executed.

4. Event Loop

  • The event loop constantly monitors both the call stack and the callback queue.

  • If the call stack is empty (i.e., no functions are being executed), the event loop takes the first callback from the callback queue and pushes it to the call stack for execution.

5. Microtask Queue (for Promises)

  • Promises use a separate queue called the microtask queue. Tasks from this queue are given higher priority than those in the callback queue.

  • When a promise resolves (e.g., with .then()), its callback is placed in the microtask queue, and it runs before tasks in the callback queue.

Code Example

console.log('Start');

// Asynchronous operation using setTimeout
setTimeout(() => {
    console.log('Timeout callback');
}, 0);

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

console.log('End');

Execution Flow:

  1. console.log('Start') is executed and prints "Start".

  2. setTimeout is encountered. Its callback is sent to the browser’s Web API and scheduled for later (after 0 milliseconds in this case), and the function continues execution.

  3. The promise is resolved, and its .then() callback is placed in the microtask queue.

  4. console.log('End') is executed, printing "End".

  5. The call stack is empty, so the event loop checks the microtask queue and runs the promise's callback, printing "Promise resolved".

  6. Finally, the event loop checks the callback queue, finds the setTimeout callback, and prints "Timeout callback".

Output:

Start
End
Promise resolved
Timeout callback

Summary:

  • The call stack executes tasks sequentially.

  • Asynchronous tasks (like setTimeout or promises) are handled outside the call stack via Web APIs.

  • Once the call stack is empty, the event loop pulls tasks from the microtask queue (for promises) first, then from the callback queue.

+-----------------+         +-----------------+
|                 |         |                 |
|   Call Stack    |<--------|  Event Loop     |
| (Executes code) |         | (Monitors stack |
|                 |         |   and queues)   |
+-----------------+         +-----------------+
        ^                              ^
        |                              |
        |                              |
+-----------------+         +--------------------+
|                 |         |                    |
|   Web APIs      |         |  Callback Queue    |
| (Async task     |         | (Holds callbacks   |
|  handling)      |-------->|   after async ops) |
|                 |         +--------------------+
+-----------------+
                                      ^
                                      |
                            +---------------------+
                            |                     |
                            |  Microtask Queue    |
                            |  (Promises resolved |
                            |   before callbacks) |
                            +---------------------+

How the flow works:

  1. Call Stack: Executes synchronous code.

  2. Web APIs: Handles async operations like setTimeout, fetch, etc., outside the call stack.

  3. Callback Queue: Stores callbacks of async tasks to be executed later.

  4. Microtask Queue: Stores promises' callbacks and is processed before the callback queue.

  5. Event Loop: Monitors the call stack and moves tasks from the queues to the stack when the stack is empty.

0
Subscribe to my newsletter

Read articles from Bhupendra directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Bhupendra
Bhupendra

I'm a passionate software developer with a strong foundation in JavaScript, TypeScript, Node.js, and React. I've honed my skills in full-stack development and building scalable, user-friendly applications. I'm driven by creating innovative solutions that solve real-world problems and enhance user experiences. I'm a quick learner, constantly updating myself with the latest industry trends and best practices. Beyond technical skills, I value collaboration, problem-solving, and maintaining a growth mindset. I'm excited to contribute my expertise to a dynamic team and make a positive impact through technology.