Home >Web Front-end >JS Tutorial >Node JS - The Event Loop
We have discussed why Node JS is single-threaded and also multi-threaded in our article called "Node Internals". It’ll give you a solid foundation on Node’s architecture and set the stage for understanding the magic of the Event Loop!
Node js could be considered single-threaded because of the Event Loop. But, what is the event loop?
I always start with the restaurant analogy because I think it becomes easy to understand technical details.
So, In the restaurant main chef takes orders from the order list and gives them to the team of assistants. When food is ready the chef serves the food. If any VIP customers come then the chef prioritize this order.
If we take this analogy into our consideration then we can say that...
In the context of Node JS Event Loop.
Chef is the Event Loop that manages tasks and delegating work.
Team of Assistance is a worker thread or the OS that handles the execution of the delegated tasks to them.
Order List is a task queue for tasks waiting for their turn.
VIP customer is a Microtask that has high priority and is completed before regular tasks.
To, Understand Event Loop we have to first understand the difference between Microtasks and Macrotasks.
Microtask means tasks that have some high priority and are executed after the currently executing Javascript code completes, but before moving to the next phase of the Event Loop.
Example:
These are lower-priority tasks queued for execution at a later phase in the Event Loop.
Example:
When we run asynchronous tasks in Node.js, the Event Loop is at the heart of everything.
Thanks to the Event Loop, Node.js can perform non-blocking I/O operations efficiently. It achieves this by delegating time-consuming tasks to the operating system or worker threads. Once the tasks are completed, their callbacks are processed in an organized manner, ensuring smooth execution without blocking the main thread.
This is the magic that allows Node.js to handle multiple tasks concurrently while still being single-threaded.
There are six phases in an Event Loop and each phase has its own queue, which holds specific types of tasks.
1.Timers phase
In this phase timer related callbacks are handled such as setTimeout, and setInterval.
Node js checks the timer queue for callbacks whose delay has expired.
If a timer delay is met, its callback is added to this queue for execution.
console.log('Start'); setTimeout(() => { console.log('Timer 1 executed after 1 second'); }, 1000); setTimeout(() => { console.log('Timer 2 executed after 0.5 seconds'); }, 500); let count = 0; const intervalId = setInterval(() => { console.log('Interval callback executed'); count++; if (count === 3) { clearInterval(intervalId); console.log('Interval cleared'); } }, 1000); console.log('End');
Output:
Start End Timer 2 executed after 0.5 seconds Timer 1 executed after 1 second Interval callback executed Interval callback executed Interval callback executed Interval cleared
2.I/O callback phase
This phase's purpose is to execute callbacks for completed I/O (Input/Output) operations, such as reading or writing files, querying databases, handling network requests, and other asynchronous I/O tasks.
When you any asynchronous I/O operation in Node.js (like reading a file using fs.readFile) comes then, the operation is delegated to the operating system or worker threads. These I/O tasks are executed outside the main thread in a non-blocking manner. Once the task is completed, a callback function is triggered to process the results.
The I/O Callbacks Phase is where these callbacks are queued for execution once the operation finishes.
const fs = require('fs'); console.log('Start'); fs.readFile('example.txt', 'utf8', (err, data) => { if (err) { console.log('Error reading file:', err); return; } console.log('File contents:', data); }); console.log('Middle'); setTimeout(() => { console.log('Simulated network request completed'); }, 0); console.log('End');
Output
Start Middle End Simulated network request completed File contents: (contents of the example.txt file)
3.Idle phase
In this phase, no user-defined work is performed instead in this phase event loop gets ready for the next phases. only internal adjustments are done in this phase.
4.Poll phase
The Poll Phase checks whether there are pending I/O events (like network activity or file system events) that need to be processed. It will immediately execute the callbacks associated with these events.
If no I/O events are pending, the Poll Phase can enter a blocking state.
In this blocking state, Node.js will simply wait for new I/O events to arrive. This blocking state is what makes Node.js non-blocking: It waits until new I/O events trigger callback executions, keeping the main thread free for other tasks in the meantime.
Any callbacks for completed I/O operations (such as fs.readFile, HTTP requests, or database queries) are executed during this phase. These I/O operations may have been initiated in previous phases (like the Timers Phase or I/O Callbacks Phase) and are now completed.
If there are timers set with setTimeout or setInterval, Node.js will check if any timers have expired and if their associated callbacks need to be executed. If timers have expired, their callbacks are moved to the callback queue, but they will not be processed until the next phase, which is the Timers Phase.
const fs = require('fs'); const https = require('https'); console.log('Start'); fs.readFile('file1.txt', 'utf8', (err, data) => { if (err) { console.log('Error reading file1:', err); return; } console.log('File1 content:', data); }); fs.readFile('file2.txt', 'utf8', (err, data) => { if (err) { console.log('Error reading file2:', err); return; } console.log('File2 content:', data); }); https.get('https://jsonplaceholder.typicode.com/todos/1', (response) => { let data = ''; response.on('data', (chunk) => { data += chunk; }); response.on('end', () => { console.log('HTTP Response:', data); }); }); console.log('End');
Output:
Start End File1 content: (contents of file1.txt) File2 content: (contents of file2.txt) HTTP Response: (JSON data from the HTTP request)
5.Check phase
After the Poll Phase has completed its tasks. This phase mainly handles the execution of setImmediate callbacks, which are scheduled to run immediately after the I/O events are processed in the Poll Phase.
setImmediate callbacks are often used when you want to perform an action after the current event loop cycle, such as making sure some task is executed after the system is not busy processing I/O events.
The Check Phase has higher priority over the Timers Phase (which handles setTimeout and setInterval). This means that setImmediate callbacks will always be executed before any timers even if their timers have expired.
setImmediate guarantees that its callback will run after the current I/O cycle and before the next timer cycle. This can be important when you want to ensure that I/O-related tasks are completed first before running other tasks.
console.log('Start'); setTimeout(() => { console.log('Timer 1 executed after 1 second'); }, 1000); setTimeout(() => { console.log('Timer 2 executed after 0.5 seconds'); }, 500); let count = 0; const intervalId = setInterval(() => { console.log('Interval callback executed'); count++; if (count === 3) { clearInterval(intervalId); console.log('Interval cleared'); } }, 1000); console.log('End');
Output:
Start End Timer 2 executed after 0.5 seconds Timer 1 executed after 1 second Interval callback executed Interval callback executed Interval callback executed Interval cleared
6.close phase
The Close Callbacks Phase typically executes when an application needs to clean up before exiting or shutting down.
This phase deals with events and tasks that need to be executed once a system resource, like a network socket or file handle, is no longer needed.
Without this phase, an application might leave open file handles, network connections, or other resources, potentially leading to memory leaks, data corruption, or other issues.
const fs = require('fs'); console.log('Start'); fs.readFile('example.txt', 'utf8', (err, data) => { if (err) { console.log('Error reading file:', err); return; } console.log('File contents:', data); }); console.log('Middle'); setTimeout(() => { console.log('Simulated network request completed'); }, 0); console.log('End');
Output:
Start Middle End Simulated network request completed File contents: (contents of the example.txt file)
There is one more special phase in the Event Loop of Node JS.
Microtask Queue
process.nextTick() and promises to execute their callbacks in a special phase in the Event Loop.
process.nextTick() schedules a callback to be executed immediately after the current operation completes, but before the event loop continues to the next phase.
process.nextTick() is not part of any phase in the event loop. Instead, it has its own internal queue that gets executed right after the currently executing synchronous code and before any phase in the event loop is entered.
It's executed after the current operation but before I/O, setTimeout, or other tasks scheduled in the event loop.
Promises have lower priority than process.nextTick() and are processed after all process.nextTick() callbacks.
const fs = require('fs'); const https = require('https'); console.log('Start'); fs.readFile('file1.txt', 'utf8', (err, data) => { if (err) { console.log('Error reading file1:', err); return; } console.log('File1 content:', data); }); fs.readFile('file2.txt', 'utf8', (err, data) => { if (err) { console.log('Error reading file2:', err); return; } console.log('File2 content:', data); }); https.get('https://jsonplaceholder.typicode.com/todos/1', (response) => { let data = ''; response.on('data', (chunk) => { data += chunk; }); response.on('end', () => { console.log('HTTP Response:', data); }); }); console.log('End');
Output:
Start End File1 content: (contents of file1.txt) File2 content: (contents of file2.txt) HTTP Response: (JSON data from the HTTP request)
Now, You have an overall idea of how the Event Loop works.
I am giving you one question which answer you can give in the comments.
const fs = require('fs'); console.log('Start'); fs.readFile('somefile.txt', 'utf8', (err, data) => { if (err) { console.error(err); return; } console.log('File content:', data); }); setImmediate(() => { console.log('Immediate callback executed'); }); setTimeout(() => { console.log('Timeout callback executed'); }, 0); console.log('End');
Thank you.
Waiting for your answer.
The above is the detailed content of Node JS - The Event Loop. For more information, please follow other related articles on the PHP Chinese website!