Node js - Event Loop

Node.js - Event Loop

Event Loop in Node.js

Introduction

The event loop is a fundamental part of Node.js architecture. It is responsible for handling asynchronous operations such as I/O tasks, timers, and callbacks. The event loop allows Node.js to perform non-blocking I/O operations despite being single-threaded. Understanding the event loop is critical to writing efficient, performant, and scalable applications in Node.js.

In this document, we will explore the concept of the Node.js event loop in detail. We will examine how it works, its phases, tasks involved, and how asynchronous code is managed using the loop. We’ll also cover key APIs like setTimeout, setImmediate, process.nextTick, and how they interact with the event loop.

What is the Event Loop?

The event loop is a mechanism that enables Node.js to perform non-blocking I/O operations by offloading operations to the system kernel whenever possible. Since most modern kernels are multithreaded, they can handle multiple operations in the background. Once an operation is complete, the kernel signals Node.js to continue with the callback.

Why the Event Loop Matters

Node.js uses a single-threaded model for executing JavaScript code. Without the event loop, Node.js would be unable to handle multiple operations concurrently, making it unsuitable for real-world server applications.

The event loop ensures that:

  • JavaScript code runs in an orderly, sequential manner
  • I/O operations like file system access and network calls do not block the main thread
  • Callbacks are executed asynchronously when their associated operations complete

How the Event Loop Works

The event loop continuously runs in a loop and processes various types of callbacks in different phases. It picks callbacks from different queues and executes them based on timing and priority.

Basic Example


console.log('Start');

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

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

console.log('End');

Output:


Start
End
Promise
Timeout

Even though `setTimeout` has a 0ms delay, the `Promise` callback runs before it due to microtask vs. macrotask prioritization.

Phases of the Event Loop

Each iteration of the event loop is called a "tick". Each tick has multiple phases, and each phase has a queue of operations to execute.

Phases in Order

  1. Timers: Callbacks scheduled by setTimeout and setInterval
  2. Pending Callbacks: I/O callbacks deferred to the next loop iteration
  3. Idle, Prepare: Internal phase, for internal use only
  4. Poll: Retrieves new I/O events
  5. Check: setImmediate() callbacks are executed here
  6. Close Callbacks: Handles close events like socket.on('close')

Visual Overview

Event Loop Cycle:


β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚     timers               β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚     pending callbacks    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚     idle, prepare        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚     poll                 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚     check                β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚     close callbacks      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

setTimeout vs setImmediate vs process.nextTick

setTimeout

Executes code after a specified delay in milliseconds.


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

setImmediate

Executes code in the check phase of the next event loop iteration.


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

process.nextTick

Executes code after the current operation but before the event loop continues. It’s part of the microtask queue.


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

Example Comparing All Three


console.log('Start');

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

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

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

console.log('End');

Expected Output:


Start
End
nextTick
setTimeout
setImmediate

Microtasks vs Macrotasks

Microtasks

Executed immediately after the current task, before the event loop continues. Examples:

  • process.nextTick
  • Promises (.then, .catch)
  • MutationObserver (browser only)

Macrotasks

Scheduled for future event loop iterations. Examples:

  • setTimeout
  • setInterval
  • setImmediate

Real-World Scenario: File Reading


const fs = require('fs');

console.log('Start');

fs.readFile('example.txt', 'utf8', (err, data) => {
  if (err) throw err;
  console.log('File read complete');
});

console.log('End');

Output:


Start
End
File read complete

The file is read asynchronously. The callback executes in the poll phase after I/O completes.

Blocking vs Non-Blocking Code

Blocking Code Example


const fs = require('fs');

console.log('Start');
const data = fs.readFileSync('example.txt', 'utf8');
console.log(data);
console.log('End');

Non-Blocking Code Example


fs.readFile('example.txt', 'utf8', (err, data) => {
  console.log(data);
});

Event Loop in Worker Threads

Node.js introduced worker threads to allow running JavaScript in parallel. Each worker has its own event loop.


const { Worker } = require('worker_threads');

const worker = new Worker(`
  setTimeout(() => {
    postMessage('Hello from worker!');
  }, 1000);
`, { eval: true });

worker.on('message', msg => console.log(msg));

Timers and Accuracy

Although you can specify a delay with setTimeout, the actual execution time depends on the event loop state.


setTimeout(() => {
  console.log('Executed later');
}, 10);

If the event loop is busy, the callback may be delayed beyond 10 milliseconds.

Handling Heavy Computation

CPU-bound tasks can block the event loop, making your app unresponsive.

Bad Practice


function heavyTask() {
  for (let i = 0; i < 1e9; i++) {}
}

heavyTask();  // Blocks entire app

Better Approach: setImmediate Chunks


function heavyTask() {
  let i = 0;
  function processChunk() {
    for (let j = 0; j < 1e5; j++) {
      i++;
    }
    if (i < 1e9) {
      setImmediate(processChunk);
    }
  }
  processChunk();
}

Tools to Visualize the Event Loop

  • Node.js --trace-events
  • Chrome DevTools (via node --inspect)
  • Loupe: A web-based event loop visualizer

Using Chrome DevTools


node --inspect-brk script.js

The event loop is the heart of Node.js and enables its non-blocking, asynchronous nature. It handles different types of operations and schedules them efficiently in multiple phases. Understanding the event loop’s internalsβ€”including how timers, I/O, microtasks, and callbacks workβ€”allows developers to write better-performing applications and avoid common pitfalls like callback hell or blocked threads.

Key concepts to remember include:

  • The event loop handles I/O asynchronously
  • Microtasks (Promises, process.nextTick) run before macrotasks (setTimeout, setImmediate)
  • Heavy CPU tasks should be offloaded or broken into smaller parts
  • Each phase of the event loop serves specific types of tasks

Mastering the event loop empowers developers to take full advantage of Node.js’s strengths in building fast, scalable, real-time applications.

Beginner 5 Hours
Node.js - Event Loop

Event Loop in Node.js

Introduction

The event loop is a fundamental part of Node.js architecture. It is responsible for handling asynchronous operations such as I/O tasks, timers, and callbacks. The event loop allows Node.js to perform non-blocking I/O operations despite being single-threaded. Understanding the event loop is critical to writing efficient, performant, and scalable applications in Node.js.

In this document, we will explore the concept of the Node.js event loop in detail. We will examine how it works, its phases, tasks involved, and how asynchronous code is managed using the loop. We’ll also cover key APIs like setTimeout, setImmediate, process.nextTick, and how they interact with the event loop.

What is the Event Loop?

The event loop is a mechanism that enables Node.js to perform non-blocking I/O operations by offloading operations to the system kernel whenever possible. Since most modern kernels are multithreaded, they can handle multiple operations in the background. Once an operation is complete, the kernel signals Node.js to continue with the callback.

Why the Event Loop Matters

Node.js uses a single-threaded model for executing JavaScript code. Without the event loop, Node.js would be unable to handle multiple operations concurrently, making it unsuitable for real-world server applications.

The event loop ensures that:

  • JavaScript code runs in an orderly, sequential manner
  • I/O operations like file system access and network calls do not block the main thread
  • Callbacks are executed asynchronously when their associated operations complete

How the Event Loop Works

The event loop continuously runs in a loop and processes various types of callbacks in different phases. It picks callbacks from different queues and executes them based on timing and priority.

Basic Example

console.log('Start'); setTimeout(() => { console.log('Timeout'); }, 0); Promise.resolve().then(() => { console.log('Promise'); }); console.log('End');

Output:

Start End Promise Timeout

Even though `setTimeout` has a 0ms delay, the `Promise` callback runs before it due to microtask vs. macrotask prioritization.

Phases of the Event Loop

Each iteration of the event loop is called a "tick". Each tick has multiple phases, and each phase has a queue of operations to execute.

Phases in Order

  1. Timers: Callbacks scheduled by setTimeout and setInterval
  2. Pending Callbacks: I/O callbacks deferred to the next loop iteration
  3. Idle, Prepare: Internal phase, for internal use only
  4. Poll: Retrieves new I/O events
  5. Check: setImmediate() callbacks are executed here
  6. Close Callbacks: Handles close events like socket.on('close')

Visual Overview

Event Loop Cycle:

┌──────────────────────────┐ │ timers │ ├──────────────────────────┤ │ pending callbacks │ ├──────────────────────────┤ │ idle, prepare │ ├──────────────────────────┤ │ poll │ ├──────────────────────────┤ │ check │ ├──────────────────────────┤ │ close callbacks │ └──────────────────────────┘

setTimeout vs setImmediate vs process.nextTick

setTimeout

Executes code after a specified delay in milliseconds.

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

setImmediate

Executes code in the check phase of the next event loop iteration.

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

process.nextTick

Executes code after the current operation but before the event loop continues. It’s part of the microtask queue.

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

Example Comparing All Three

console.log('Start'); setTimeout(() => { console.log('setTimeout'); }, 0); setImmediate(() => { console.log('setImmediate'); }); process.nextTick(() => { console.log('nextTick'); }); console.log('End');

Expected Output:

Start End nextTick setTimeout setImmediate

Microtasks vs Macrotasks

Microtasks

Executed immediately after the current task, before the event loop continues. Examples:

  • process.nextTick
  • Promises (.then, .catch)
  • MutationObserver (browser only)

Macrotasks

Scheduled for future event loop iterations. Examples:

  • setTimeout
  • setInterval
  • setImmediate

Real-World Scenario: File Reading

const fs = require('fs'); console.log('Start'); fs.readFile('example.txt', 'utf8', (err, data) => { if (err) throw err; console.log('File read complete'); }); console.log('End');

Output:

Start End File read complete

The file is read asynchronously. The callback executes in the poll phase after I/O completes.

Blocking vs Non-Blocking Code

Blocking Code Example

const fs = require('fs'); console.log('Start'); const data = fs.readFileSync('example.txt', 'utf8'); console.log(data); console.log('End');

Non-Blocking Code Example

fs.readFile('example.txt', 'utf8', (err, data) => { console.log(data); });

Event Loop in Worker Threads

Node.js introduced worker threads to allow running JavaScript in parallel. Each worker has its own event loop.

const { Worker } = require('worker_threads'); const worker = new Worker(` setTimeout(() => { postMessage('Hello from worker!'); }, 1000); `, { eval: true }); worker.on('message', msg => console.log(msg));

Timers and Accuracy

Although you can specify a delay with setTimeout, the actual execution time depends on the event loop state.

setTimeout(() => { console.log('Executed later'); }, 10);

If the event loop is busy, the callback may be delayed beyond 10 milliseconds.

Handling Heavy Computation

CPU-bound tasks can block the event loop, making your app unresponsive.

Bad Practice

function heavyTask() { for (let i = 0; i < 1e9; i++) {} } heavyTask(); // Blocks entire app

Better Approach: setImmediate Chunks

function heavyTask() { let i = 0; function processChunk() { for (let j = 0; j < 1e5; j++) { i++; } if (i < 1e9) { setImmediate(processChunk); } } processChunk(); }

Tools to Visualize the Event Loop

  • Node.js --trace-events
  • Chrome DevTools (via node --inspect)
  • Loupe: A web-based event loop visualizer

Using Chrome DevTools

node --inspect-brk script.js

The event loop is the heart of Node.js and enables its non-blocking, asynchronous nature. It handles different types of operations and schedules them efficiently in multiple phases. Understanding the event loop’s internals—including how timers, I/O, microtasks, and callbacks work—allows developers to write better-performing applications and avoid common pitfalls like callback hell or blocked threads.

Key concepts to remember include:

  • The event loop handles I/O asynchronously
  • Microtasks (Promises, process.nextTick) run before macrotasks (setTimeout, setImmediate)
  • Heavy CPU tasks should be offloaded or broken into smaller parts
  • Each phase of the event loop serves specific types of tasks

Mastering the event loop empowers developers to take full advantage of Node.js’s strengths in building fast, scalable, real-time applications.

Related Tutorials

Frequently Asked Questions for Node.js

A function passed as an argument and executed later.

Runs multiple instances to utilize multi-core systems.

Reusable blocks of code, exported and imported using require() or import.

nextTick() executes before setImmediate() in the event loop.

Starts a server and listens on specified port.

Node Package Manager β€” installs, manages, and shares JavaScript packages.

A minimal and flexible web application framework for Node.js.

A stream handles reading or writing data continuously.

It processes asynchronous callbacks and non-blocking I/O operations efficiently.

Node.js is a JavaScript runtime built on Chrome's V8 engine for server-side scripting.

An object representing the eventual completion or failure of an asynchronous operation.

require is CommonJS; import is ES6 syntax (requires transpilation or newer versions).

Use module.exports or exports.functionName.

Variables stored outside the code for configuration, accessed using process.env.


MongoDB, often used with Mongoose for schema management.

Describes project details and manages dependencies and scripts.

Synchronous blocks execution; asynchronous runs in background without blocking.

Allows or restricts resources shared between different origins.

Use try-catch, error events, or middleware for error handling.

Provides file system-related operations like read, write, delete.

Using event-driven architecture and non-blocking I/O.

Functions in Express that execute during request-response cycle.

A set of routes or endpoints to interact with server logic or databases.

Yes, it's single-threaded but handles concurrency using the event loop and asynchronous callbacks.

Middleware to parse incoming request bodies, like JSON or form data.

line

Copyrights © 2024 letsupdateskills All rights reserved