We know that a Node.js application is run in a single-threaded fashion. But, it can handle multiple asynchronous operations in the background and exhibits features as if it is a multi-threaded application. At the base level, Node.js is built on C++ which actually allows the existence of multiple threads. While that's not the actual basis, Node.js utilizes the libuv
library that allows it to interact with the operating system and utilize available resources efficiently. The library enables asynchronous I/O operations such as file reading, database querying, data transferring over the network, and so on, then it will trigger the registered callback for each completed I/O operation to run. Node.js manage all the callbacks in a mechanism called "event loop".
An event loop is a loop of sequential processes which are grouped into several phases. It handles callbacks of asynchronous I/O operations and asynchronous calls initiated by objects or functions in the main application itself. The phases are as follows.
1. Poll
In this phase, the callbacks from I/O operations are executed. The functions from the main scope of the application are also executed here. There are also two primary microtasks here. The first is the execution of functions called by process.nextTick()
that have the highest priority. The second is the execution of callbacks fired by resolve
or reject
of the Promises. It has a higher priority compared to the callback from I/O operations.
2. Check
This phase will check whether callbacks from the setImmediate()
function exists.
3. Close
It will execute callbacks of the close
events that are fired by EventEmitter
.
4. Timers
In this phase, callbacks of the setTimeout()
and setInterval()
are called.
5. Pending
Callbacks of specific system events are called in this phase.
Phases of Event Loop |
All registered callbacks in a phase will be completely executed before going to the next phase. Let's take a look at the following example.
const fs = require('fs');
setImmediate(() => { console.log('immediate'); });
Promise.resolve().then(() => { console.log('resolve'); });
process.nextTick(() => { console.log('nexttick1'); });
process.nextTick(() => { console.log('nexttick2'); });
fs.readFile(__filename, () => {
console.log('readfile');
setTimeout(() => { console.log('timeout'); });
setImmediate(() => { console.log('immediate2'); });
});
If we align with the phases described before, the result would be:
nexttick1
nexttick2
resolve
immediate
readfile
immediate2
timeout
Notice that callback of the fs.readFile()
is run the last while it would be in the poll phase as an I/O operation callback. It is because the callback is registered in the event loop when the actual I/O operation which is reading a file has been completed and returns the result. Meanwhile, the process.nextTick()
method which is called in the third order comes out first. The last setImmediate()
method comes before the setTimeout()
method, aligning with the phase order.
Comments
Post a Comment