Understanding Asynchronous Programming in Node.js

18 August, 2024

NodeJS

Node.js is a powerful, single-threaded JavaScript runtime designed for building server-side applications. While it operates on a single thread by design, Node.js is optimized to handle multiple threads for CPU-intensive tasks, ensuring smooth and non-blocking performance.

To help visualize how Node.js manages asynchronous operations, I’ve created the diagrams below. It offers a quick reference for revisiting the concept, but I encourage you to dive deeper into how Node.js orchestrates async tasks.

Node.js has two major components

  • v8: JavaScript runtime
  • libuv: C library that provides support for asynchronous I/O based on event loops.

cmd > node index.js

  • when we run a file in node a main thread is created

Main thread

This thread executes the code in this order:

  • Top-level code: It executes module imports, object definitions, and variable declarations.
  • Event Callbacks: Event callbacks get registered.
  • Event Loop: The event loop is started to manage asynchronous operations.

Thread pool

In addition to the main thread, Node.js utilizes a thread pool provided by libuv. These threads handle CPU-intensive tasks such as:

  • File system operations (fs)
  • Encryption
  • Compression

By default, the thread pool contains four threads. However, it can be configured to include up to a maximum of 128 threads.


Callback prioritization

The diagram below illustrates callback prioritization in Node.js:


Node.js handles tasks through two primary systems:

OS Handles: Asynchronous, non-blocking tasks like network I/O, timers, and non-blocking DNS lookups, thread management.

libuv Thread Pool Handles: Blocking or CPU-intensive tasks like file I/O, blocking DNS lookups, cryptography, and compression.


Life cycle of code execution in NodeJS

The following diagram showcases the life cycle of code execution in Node.js:

Link to the diagrams