Whenever the call stack receives asynchronous operations such as setTimeout, setImmediate, API calls, or file system operations, Node.js offloads these tasks to libuv.
libuv is a C library that manages how asynchronous tasks should be executed. It interacts with the operating system’s kernel when needed to perform tasks like I/O operations efficiently.
The event loop is what allows Node.js (via libuv) to coordinate and manage these asynchronous operations. The event loop has several phases where different types of tasks are processed. In total, there are six phases, though four are the main ones typically mentioned:
Timers phase:
Executes callbacks scheduled by setTimeout() and setInterval(). I/O callbacks phase: Handles I/O-related callbacks, like reading or writing to files or networks. Check phase: Executes callbacks scheduled by setImmediate(). Close callbacks phase: Executes callbacks for tasks that have been closed, like socket.destroy(), or when a server is stopped with server.close().
Apart from these, there are two microtask queues that handle tasks like:
process.nextTick()Promise callbacksThe microtask queue has priority over the event loop phases and is processed after the current operation in the call stack is complete and before moving on to the next phase of the event loop.
Sequence and Execution
The event loop runs continuously, checking if any tasks are pending in any of the phases. If no tasks are ready, it sits in the poll phase (which is part of the I/O phase) waiting for I/O operations to complete or timeouts to expire.
If any phase contains multiple callbacks, the event loop will first process them one at a time until the queue is empty or a system-defined limit is reached, before moving to the next phase.
Exection of setTimeout(cb,0) and setImmediate(cb) is non-deterministic. It depends on the performance of the process.
But since eventloop sits in poll phase, then setImmediate will always execute first.