Blocking and non-blocking calls in Node.js

NAVIGATION

Accessing external resources

Blocking

Non-blocking

Blocking vs non-blocking

You know that Node.js is based on an event-driven non-blocking I/O model. You may be wondering what does it mean for something to be blocking or something not to be blocking. In fact, there are both types of operations available in Node.js. However, in order to create a performant applications, only one kind should be used.

Accessing external resources

Blocking or non-blocking operating model is relevant when dealing with input/output operations. Writing to a resource or reading from a resource are considered I/O operations. The targets for such operations can be anything from files to network sockets to screen to keyboard. Any resource that is not accessible instantly at the machine instruction level but involves waiting instead requires I/O communication.

Computer memory, on the other hand, can be addressed instantly and does not require waiting. Accessing memory is therefore not considered an I/O operation. In fact, the random-access memory (RAM) name originates from its ability to access any location in the same amount of time irrespective of its physical location.

Blocking

Process state diagram
Operating system labels processes with a state based on how ready they are to be put on CPU.
Source: TechnologyUK Process Management.

Blocking term originates from the operating system process model. A multitasking operating system labels each process with a state depending on how ready they are to be put on the CPU for execution. A process is labeled blocked if it is not ready for execution but is instead waiting for an I/O event to take place. I/O events indicate progress or completion in an I/O operation, for example "resource available" or "write complete".

Performing a blocking system call causes the process to enter the blocked state. Control is let back to the process only after the I/O event that is being waited upon occurs.

In Node.js the filesystem module contains fs.xSync methods that provide blocking versions of the filesystem operations.

const fs = require("fs");
const contents = fs.readFileSync("file.txt", "utf8");
// this line is not reached until the read results are in
console.log(contents);

Using blocking version, the call will not return until the I/O is completed. When the call returns, it provides the results for the operation as return value. The results are returned synchronously.

A blocking call causes results to be returned synchronously.

Non-blocking

A non-blocking operation does not wait for I/O to complete. When blocking operation caused the process to be put on the back burner at the operating system level, performing a non-blocking I/O operation the process continues to run instead. A non-blocking call initiates the operation, leaves it for the operating system to complete returning immediately without any results. Alternate means are then used to determine completion of the I/O operation.

There are few ways to communicate that a non-blocking I/O operation has completed. Programs interacting closer with operating systems use polling. With polling, the program repeatedly asks from the operating system: has any I/O operation completed and then processes those that have. In JavaScript another approach is used with callbacks. A non-blocking call in JavaScript provides a callback function that is to be called when the operation is complete. Node.js internally uses operating system level polling in combination with worker threads for operations that do not support polling. Node then translates these mechanisms into JavaScript callbacks.

A performant application uses non-blocking I/O operations. This allows a single process to serve multiple requests at the same time. Instead of process being blocked and waiting for single I/O operation to complete, this waiting time is used to serve other requests. All Node.js libraries and the Node core api offer non-blocking operations with few minor exceptions.

The filesystem example written using a non-blocking call.

const fs = require("fs");
fs.readFile("file.txt", "utf8", (err, data) => {
  // called at a later time when the results are in
  console.log(data);
});
// readFile returns immediately and this line is reached right away

In this version, the readFile call returns immediately without any results. The current code block returns and only after the I/O operations are complete will the callback be called. It is said that the results are returned asynchronously.

A non-blocking call causes results to be returned asynchronously.

Blocking vs non-blocking

Blocking call waits for the I/O operation to complete before returning. Its results are returned synchronously. Nothing else in that process takes place during the waiting. In contrast, non-blocking call returns immediately without results and uses alternate means to check for completion. Other processing can be done while waiting and the results are returned asynchronously. Node.js libraries and core api provide non-blocking calls that can be used to build performant applications. Such applications make use of I/O waiting time to serve other requests.

Node doesn't wait for your database call to finish?

Node doesn't wait for your database call to finish?

Learn how asynchronous calls work and make your app run as you intended. Get short email course on asynchronicity and two chapters from Finish Your Node App.

Loading Comments