Event Loop in Node.js – more detail

The Event Loop is a core concept in Node.js that allows it to handle asynchronous operations on a single thread efficiently. This model is crucial for understanding how Node.js performs non-blocking I/O operations and manages callbacks, timers, and other asynchronous tasks.

1. What is the Event Loop?

The Event Loop is a mechanism in Node.js that processes and handles:

  • I/O operations (e.g., reading files, database queries).
  • Timers (setTimeout, setInterval).
  • Callbacks from asynchronous operations.
  • Non-blocking functions in the JavaScript runtime.

Despite being single-threaded, Node.js can perform concurrent operations by delegating tasks to the underlying libuv library, which uses a thread pool for heavy tasks (e.g., file I/O).

2. How the Event Loop Works

The Event Loop has six phases, processed sequentially in a loop. Each phase has a specific purpose and processes a particular type of callback.

Phases of the Event Loop:

  1. Timers Phase:
    • Executes callbacks scheduled by setTimeout and setInterval.
    • Only executes if their delay has expired.
  2. Pending Callbacks Phase:
    • Processes callbacks for I/O operations that were deferred (e.g., TCP errors).
  3. Idle, Prepare Phase:
    • Internal operations used by Node.js, generally ignored in application code.
  4. Poll Phase:
    • Retrieves new I/O events and executes their callbacks.
    • Completes when the queue is empty or the maximum timer expires.
  5. Check Phase:
    • Executes callbacks scheduled by setImmediate.
  6. Close Callbacks Phase:
    • Executes callbacks for closed connections (e.g., socket.on('close')).

Key Note:

Between these phases, the Event Loop checks the microtask queue (promises and process.nextTick callbacks) and executes them before proceeding to the next phase.

3. Node.js Single-Threaded Model

Node.js uses a single JavaScript thread for executing code, relying on the Event Loop to manage asynchronous tasks. This single-threaded nature has some implications:

  • Non-blocking: While the JavaScript thread handles callbacks, heavy tasks like file I/O or database queries are offloaded to the libuv thread pool.
  • Concurrency: Though single-threaded, Node.js achieves concurrency by delegating tasks to the thread pool and managing callbacks through the Event Loop.

4. Details of Asynchronous Functions

process.nextTick

  • Adds a callback to the microtask queue.
  • Executed immediately after the current operation completes, before moving to the next phase of the Event Loop.

setTimeout and setInterval

  • Scheduled in the Timers Phase.
  • If the specified delay has passed, the callback is executed.
  • Note: The delay is a minimum threshold, not guaranteed.

setImmediate

  • Scheduled in the Check Phase.
  • Executes callbacks after I/O events in the Poll Phase.

Promises (Microtasks)

  • Scheduled in the microtask queue, which has higher priority than the phases in the Event Loop.
  • Executed immediately after the current operation completes, but before timers or I/O callbacks.

I/O Operations

  • Managed in the Poll Phase.
  • Heavy operations are offloaded to the libuv thread pool.

5. Example of Event Loop Behavior

Here’s an example illustrating how process.nextTick, setTimeout, setImmediate, and Promises interact in the Event Loop:

console.log('Start');

setTimeout(() => {
  console.log('setTimeout');
}, 0);

setImmediate(() => {
  console.log('setImmediate');
});

Promise.resolve().then(() => {
  console.log('Promise');
});

process.nextTick(() => {
  console.log('nextTick');
});

console.log('End');

Output:

Start
End
nextTick
Promise
setTimeout
setImmediate

Explanation:

  1. console.log('Start') and console.log('End') execute synchronously.
  2. process.nextTick and Promises are added to the microtask queue and executed immediately after the current operation.
  3. setTimeout and setImmediate are scheduled for their respective phases in the Event Loop.

Conclusion

The Event Loop is the heart of Node.js, enabling it to handle asynchronous operations efficiently on a single thread. By understanding how process.nextTick, setTimeout, Promises, and other asynchronous constructs work, developers can write performant, non-blocking applications in Node.js.

Leave a comment

I’m Tran Minh

Hi, I’m Trần Minh, a Solution Architect passionate about crafting innovative and efficient solutions that make technology work seamlessly for you. Whether you’re here to explore the latest in tech or just to get inspired, I hope you find something that sparks joy and curiosity. Let’s embark on this exciting journey together!

Let’s connect