Understanding Event Loop

Now that we have ruled out basic misconceptions regarding Event Loop in Node.js, let's look at the workings of Event Loop in detail and all the phases that are in the Event Loop phase execution cycle. Node.js processes everything occurring in the environment in the following phases:

  • Timers: This is the phase where all the setTimeout() and setInterval() callbacks are executed. This phase will run early because it has to be executed in the time interval specified in the calling functions. When the timer is scheduled, then as long as the timer is active the Node.js Event Loop will continue to run.
  • I/O callbacks: Most common callbacks are executed here except timers, close connection events, setImmediate(). An I/O request can be blocking as well as non-blocking. It executes more things such as connection error, failed to connect to a database, and so on.
  • Poll: This phase executes the scripts for timers when the threshold has elapsed. It processes events maintained in the poll queue. If the poll queue is not empty, the Event Loop will iterate through the entire queue synchronously until the queue empties out or the system hard peak size is reached. If the poll queue is empty, the Event Loop continues with the next phase—it checks and executes those timers. If there are no timers, the poll queue is free; it waits for the next callback and executes it immediately.
  • Check: When the poll phase is idle, the check phase is executed. Scripts that have been queued with setImmediate() will be executed now. setImmediate() is a special timer that has use of the libuv API and it schedules callbacks to be executed after the poll phase. It is designed in such a way that it executes after the poll phase.
  • Close callbacks: When any handle, socket, or connection is closed abruptly, the close event is emitted in this phase, such as socket.destroy(), connection close(), that is, all on (close) event callbacks are processed here. Not technically parts of the Event Loop, but two other major phases are nextTickQueue and other micro tasks queue. The nextTickQueue processes after the current operation gets completed, regardless of the phase of Event Loop. It is fired immediately, in the same phase it was called, and is independent from all phases. The nextTick function can contain any tasks and they are just invoked as follows:
process.nextTick(() => {
console.log('next Tick')
})

The next important part is the micro and the macro tasks. NextTickQueue has higher priority over micro and macro tasks. Any task that is in nextTickQueue will be executed first. Micro tasks include functions such as resolved promise callbacks. Some examples of micro tasks can be promise.resolve, Object.resolve. An interesting point to note here is native promises only come under micro tasks. If we use libraries such as q or bluebird, we will see them getting resolved first.