Skip to main content

Blocking and Non-Blocking in Node.js - Asynchronous Operations and Callbacks

The asynchronous coding paradigm enables us to write non-blocking code. This makes the single threaded Javascript run with efficiency. A single thread is like an execution that can do only one thing at a time. Node.js smartly uses this single thread to get non-blocking execution. We will understand blocking and non-blocking now.
function doTask1(){
 // do something here
}

function doTask2(){
 // do something else here
}

// perform some task
doTask1()
doTask2()
In this example code doTask1()function executes first and after it returns then doTask2() function executes. This is not a blocking code in javascript even if doTask1 takes a long time before returning (it could be performing some CPU intensive tasks like finding the inverse of a matrix).

Blocking

When javascript execution in Node.js process (each program is a process) has to wait until a non-javascript operation completes is called blocking.

Non-Blocking

This is the opposite of the blocking i.e. javascript execution do not wait until the non-javascript operation completes.
Non-Javascript execution refers to mainly I/O operations. So, in the nutshell, I/O operations are blocking.
I/O refers primarily to the interaction with the system's disk and network.

Now, let's take another example:

function doTask1(){
 const users = getAllUsers() 
 // do something with users here
}

function doTask2(){
 const services = getAllServices()
 // do something with services here
}

// perform some task
doTask1()
doTask2()
In this example, doTask1internally calls getAllUserwhich makes the database connection and fetch the list of users. Also, doTask2 internally calls getAllServices which makes an HTTP request to get the list of available services of some 3rd party through their API.
Here doTask2 will be blocked till doTask1 returns because a single thread can execute only one thing at a time.
Now, you must be wondering that how Node.js converts this blocking calls into a non-blocking execution.
Node.js uses the event loop and callback mechanism to lift off I/O operations from the javascript's thread to the system's kernel.
Since most modern kernels are multi-threaded, they can handle multiple operations executing in the background concurrenlty. When one of these operations completes, the kernel notifies Node.js and then the appropriate callback is eventually executed.
lets see how we can make this blocking code into non-blocking in Node.js using callbacks.
function doTask1() {
 const users = getAllUsers((err, data) => {
   // do something with users here
 })
}

function doTask2() {
 const services = getAllServices((err, data) => {
   // do something with services here
 })
}

// perform some task
doTask1()
doTask2()
Here, getAllUsers and getAllServices now take callbacks. These callbacks are then used by the Node.js and called when the Kernel is finished with the I/O operations.

The structure of callback in Node.js

A callback is a javascript function, which is called at the completion of a given task. Node.js has some convention for this callback function as listed below:
  1. The callback is passed as the last parameter to any function.
  2. The callback gets called after the function is done with all of its operations.
  3. The first parameter of the callback is the error value. If there is no error then the first parameter is set to null and rest being the return value.
function doTask1(callback) {
 const users = getAllUsers((err, data) => {
   // do something here
   if (err) return callback(err)
   // do something more here
   callback(null, data)
 })
}
You must have understood how Node.js is able to make asynchronous calls for non-blocking operations using callbacks. But even more interesting thing to understand is how callbacks work w.r.t I/O and when it is exactly executed. We will understand this when we explore the event-loop.
Node.js takes a strong stand for concruency using single threaded system. It advocates that threads based concruency is relatively inefficient and very difficult to use. Using a single thread also prevents the users of Node.js from worries of dead-locking (2 or more threads wait on the locks in a cyclic manner and non of them are able to free the locks)the process, since there are no locks.
Node.js makes the framwork very fast and efficient and one of the reason is that, no function in Node.js directly performs I/O, so the process never blocks. The callbacks used in these asynchronous calls allows you to have as many I/O operations as your OS can handle, happening simultaneously.
Many functions in the Node.js provides both blocking and non-blocking variants.
  1. The non-blocking version takes a callback function as a parameter.
  2. Some blocking counterpart names generally end with Sync. These functions execute synchronously and block the code.
Example of using the file system in both modes:
Synchronous file read:
const fs = require('fs');
const data = fs.readFileSync('/file.html'); // blocks here until file is read
console.log(data);
doSomethingElse(); // will run after console.log
Note: In synchronous version if an error is thrown then it will have to be caught else the process will crash.
Asynchronous file read:
const fs = require('fs');
fs.readFile('/file.html', (err, data) => {
 if (err) throw err;
 console.log(data);
});
doSomethingElse(); // will run before console.log
Note: In asynschronous version author is responsible for choosing the way of error handlling. We should always choose non-blocking version over the blocking varient of the function.

Comments

Popular posts from this blog

4 Ways to Communicate Across Browser Tabs in Realtime

1. Local Storage Events You might have already used LocalStorage, which is accessible across Tabs within the same application origin. But do you know that it also supports events? You can use this feature to communicate across Browser Tabs, where other Tabs will receive the event once the storage is updated. For example, let’s say in one Tab, we execute the following JavaScript code. window.localStorage.setItem("loggedIn", "true"); The other Tabs which listen to the event will receive it, as shown below. window.addEventListener('storage', (event) => { if (event.storageArea != localStorage) return; if (event.key === 'loggedIn') { // Do something with event.newValue } }); 2. Broadcast Channel API The Broadcast Channel API allows communication between Tabs, Windows, Frames, Iframes, and  Web Workers . One Tab can create and post to a channel as follows. const channel = new BroadcastChannel('app-data'); channel.postMessage(data); And oth...

Certbot SSL configuration in ubuntu

  Introduction Let’s Encrypt is a Certificate Authority (CA) that provides an easy way to obtain and install free  TLS/SSL certificates , thereby enabling encrypted HTTPS on web servers. It simplifies the process by providing a software client, Certbot, that attempts to automate most (if not all) of the required steps. Currently, the entire process of obtaining and installing a certificate is fully automated on both Apache and Nginx. In this tutorial, you will use Certbot to obtain a free SSL certificate for Apache on Ubuntu 18.04 and set up your certificate to renew automatically. This tutorial will use a separate Apache virtual host file instead of the default configuration file.  We recommend  creating new Apache virtual host files for each domain because it helps to avoid common mistakes and maintains the default files as a fallback configuration. Prerequisites To follow this tutorial, you will need: One Ubuntu 18.04 server set up by following this  initial ...

Working with Node.js streams

  Introduction Streams are one of the major features that most Node.js applications rely on, especially when handling HTTP requests, reading/writing files, and making socket communications. Streams are very predictable since we can always expect data, error, and end events when using streams. This article will teach Node developers how to use streams to efficiently handle large amounts of data. This is a typical real-world challenge faced by Node developers when they have to deal with a large data source, and it may not be feasible to process this data all at once. This article will cover the following topics: Types of streams When to adopt Node.js streams Batching Composing streams in Node.js Transforming data with transform streams Piping streams Error handling Node.js streams Types of streams The following are four main types of streams in Node.js: Readable streams: The readable stream is responsible for reading data from a source file Writable streams: The writable stream is re...