Page Content

Tutorials

What is the Event Loop Call Stack Callback Queue?

How Node.js Manages Asynchronous Code

Only one piece of code is run at a time while using Node.js since it operates on a single thread. Node.js uses an asynchronous, event-driven methodology to prevent blocking this single thread during time-consuming tasks like file I/O or network requests.

The main elements work together as follows:

Call Stack

This data structure, which tracks the program’s execution, is Last In, First Out (LIFO). A function is added to the top of the Call Stack when it is called. When the function is finished, it is taken off the top. Code that is synchronous runs directly on the Call Stack. The entire application is blocked if a function on the Call Stack takes too long to run.

Node APIs (or Web APIs in browser context)

Node.js environment (or browser) functions that manage asynchronous operations like setTimeout(), file system operations (fs module), and HTTP requests (http module) are known as Node APIs (or Web APIs in browser context). The Call Stack can proceed with other code right away without waiting for the asynchronous action to finish because when an asynchronous function is called, it is delegated to these APIs.

Callback Queue (also called Message Queue)

The Callback Queue is where the callback function for an asynchronous operation assigned to a Node API is relocated after it is finished (for example, a timer expires, a file is read, or a network response is received). A First In, First Out (FIFO) data structure describes this queue.

Event Loop

The core of Node.js’s asynchronous functionality is the Event Loop. The Event Loop keeps checking to see if the Call Stack is full. The first callback from the Callback Queue is then pushed into the Call Stack for execution if the Call Stack is empty. This feature makes sure Node.js doesn’t block.

An Example of Event Loop Interaction to Show:

The following is a basic Node.js script that uses both synchronous and asynchronous calls:

// Example Script: event_loop_demo.js

console.log('1. Starting Program'); // Synchronous code

setTimeout(() => {
  console.log('4. setTimeout callback (0ms delay)'); // Callback for 0ms timer
}, 0);

setTimeout(() => {
  console.log('5. setTimeout callback (2000ms delay)'); // Callback for 2000ms timer
}, 2000);

process.nextTick(() => {
  console.log('3. process.nextTick callback'); // NextTick callback
});

setImmediate(() => {
  console.log('6. setImmediate callback'); // setImmediate callback
});

const fs = require('fs');
fs.readFile(__filename, (err, data) => {
  console.log('7. File Read callback'); // I/O callback
});

console.log('2. Ending Program Synchronously'); // Synchronous code

Output of node event_loop_demo.js:

1. Starting Program
2. Ending Program Synchronously
3. process.nextTick callback
4. setTimeout callback (0ms delay)
7. File Read callback
6. setImmediate callback
5. setTimeout callback (2000ms delay)

Explanation of Output:

  • 1. Starting Program and 2. Ending Program Synchronously are printed synchronously. From top to bottom, these synchronous activities are carried out directly on the Call Stack.
  • procedure.nextTick: The callback for process.nextTick() is placed at the head of the event queue and is processed entirely before I/O or timer events, but after the execution of the current script. This is why 3. process.nextTick callback appears next.
  • Call Stack Empties: The Call Stack is left empty following the completion of all synchronous code and nextTick callbacks. The Event Loop will begin monitoring the Callback Queue at this point.
  • setTimeout(0): SetTimeout() callbacks are added to the Callback Queue even when there is a 0 ms delay. When the Call Stack is clear, the Event Loop retrieves it and prints the 4. setTimeout callback (0ms delay).
  • File Read Callback (fs.readFile): Asynchronous file I/O is used. Node APIs (more especially, libuv, which is multi-threaded internally) handle the fs.readFile action, and after the file read is finished, its callback is queued. It is typically processed after timers or within the same phase, though the precise timing in relation to setTimeout(0) may vary slightly based on system load. Here, it prints 7. File Read callback.
  • setImmediate: I/O and timer events are followed by callbacks queued by setImmediate(). 6. setImmediate callback is therefore displayed later in the output.
  • setTimeout(2000): After its 2000ms wait is up, this callback is added to the Callback Queue. When it’s time, the Event Loop processes it, printing 5. setTimeout callback (2000ms delay) comes last among the callbacks because it has the longest explicit delay.

Timers: and

  • The asynchronous nature of Node.js and its relationship to the Event Loop are best illustrated via the setTimeout() and setInterval() functions.
  • setTimeout(callback, delay): This function plans the execution of a callback function after a given millisecond delay. The timer is set in the Node APIs (off the Call Stack) when setTimeout is used. Once the delay expires, the callback function is moved to the Callback Queue. Only when the Call Stack is empty will it be sent there for execution. SetTimeout(callback, 0) implies “execute as soon as the current synchronous code finishes and the Call Stack is clear” rather than “execute immediately” for this reason.
  • Similar to setTimeout, setInterval(callback, delay) periodically runs the callback function at the provided intervals of delay. Node APIs handle setInterval operations asynchronously, just like setTimeout. SetInterval callbacks may lose their exact timing if a blocking synchronous activity takes up the Call Stack. Instead, they may queue up and then be quickly executed one after the other when the Call Stack is free. In contrast to browser contexts, setInterval is typically more dependable in Node.js environments.

Analogy

Imagine the Call Stack as a post office with just one clerk.

  • The clerk takes care of your quick tasks right away, such as purchasing a stamp.
  • The clerk doesn’t pause everything to weigh and process a bulky parcel that needs to be sent overseas (an asynchronous procedure). Rather, they delegate it to the back-end special foreign mail department (Node APIs).
  • A notification that your assignment is ready is posted on a bulletin board (Callback Queue) by the international mail department once they have completed processing your shipment.
  • The clerk is continuously observed by the supervisor (Event Loop). The supervisor checks the bulletin board to see if the clerk is available (the Call Stack is empty). They take the oldest note, if there are any, and give it to the clerk to fill up. In this manner, even though certain duties take longer to complete, the clerk is always active.
Index