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
and2. 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 forprocess.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 why3. 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 the4. 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 thefs.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 tosetTimeout(0)
may vary slightly based on system load. Here, it prints7. File Read callback
. setImmediate
: I/O and timer events are followed by callbacks queued bysetImmediate()
.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, printing5. 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()
andsetInterval()
functions. setTimeout(callback, delay)
: This function plans the execution of acallback
function after a given milliseconddelay
. The timer is set in the Node APIs (off the Call Stack) whensetTimeout
is used. Once thedelay
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 thecallback
function at the provided intervals of delay. Node APIs handlesetInterval
operations asynchronously, just likesetTimeout
.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.