Callbacks in JavaScript — Part 2

Sunilkathuria
In Computing World
Published in
6 min readApr 10, 2024

In one of my articles, I introduced callbacks in JavaScript. In this article, I will take this discussion further and discuss “Promise” to address the callback hell problem.

Callback hell

Looking at the callback hell, the code below reads three files, one after the other. All three read operations are asynchronous. The data is written to another file after the files are read.

When a callback function implements another callback within itself, it leads to callback hell.

The above code snippet is not easily readable and maintainable.

JavaScript provides a " Promise " feature to write easily readable and manageable asynchronous code.

Using Promise

The following code snippet shows the above implementation using Promise.

All three files are read in their respective function calls. In the earlier implementation, the read operation was implemented in a cascading manner, which made it difficult to read and maintain. Imagine how challenging it would be to add a new function to read another file between a second and third file in either case.

When the same logic is implemented using Promise, the code is executed step-by-step, making it easy to read and manage.

Promise implements error handling more cleanly. There is one “.catch” statement against three separate error-handling statements. Let’s now understand how to use Promise.

What is a Promise

Promise offers a cleaner way to manage asynchronous operations. It is an object that represents the completion or failure of an asynchronous operation. Promise connects two parts of a program: the asynchronous code and the other part of the code, which depends on the asynchronous operation to complete, called the consumer.

States of a Promise

Pending: This is an object’s initial state. It indicates that an asynchronous operation is in progress. Depending on the operation’s result, it is either fulfilled or rejected.

Fulfilled: Indicates that the asynchronous operation is a success.

Rejected: Indicates that the asynchronous operation is a failure.

The executor

The Promise constructor accepts a callback function, which performs an asynchronous operation and is also known as an executor. The syntax of a promise object is as follows. promiseObj is the new Promise object.

Promise constructor accepts a callback function as a parameter called an executorFunction. You can give it any name. This function executes the asynchronous code.

The function should accept two arguments. These arguments are callbacks that JavaScript provides. The names resolve and reject are just conventions. You can give any name to these.

Once the asynchronous code completes its execution, the executorFunction must call resolve when the execution succeeds or call reject when it fails.

The resolve function is called with “value” data that is passed on to the code waiting for completion of asynchronous code.

The reject function is called with “error”, which the error handling mechanism manages.

When resolve is called, the state of the Promise changes from Pending to fulfilled.

When reject is called, the state of Promise changes from Pending to Rejected.

Once a promise reaches a fulfilled or Rejected state, it cannot change to the other state.

When a Promise is created, it is in a pending state. When it changes from pending to fulfilled or rejected, it is resolved.

Let’s understand the function readFirstFileAsync.

This is the executorFunction, and argument to the Promise constructor. It accepts two arguments, resolve and reject, and implements an asynchronous operation fs.readFile.

When the file read operation causes an error, the reject callback executes with the error as an argument. Otherwise, the resolve callback executes with the data as an argument.

In both cases, this information (err or data) is passed to the consumer code, which depends on the execution of this asynchronous code.

Consumer

Once a Promise is resolved, the information is available to the consumer(s). These are .then, .catch, and .finally.

.then()

When a Promise is fulfilled, we can call the .then method of the promise object, which connects a Promise with its consumer.

.then method takes two arguments. Both are callback functions and optional. These are onFulfilled() and onRejected(). These names are conventions; you can use any other names. It returns a new Promise. Following is the syntax

promise.then(onFulfilled, onRejected)

Based on the code snippet below, all the functions passed as an argument to .then are consumers. The resolve function at each step passes the data to the next sequence step. For example, the function showData receives the data from then(readThirdFileAsync). showData is responsible for displaying the data on the console.

A promise object with one or more “.then” and/or “.catch” statements is called a promise chain.

Note: It is possble to manage the error situation by passing another callback function as an argument to the “.then” method. This is excluded to make this example simple to understand.

.catch()

When a Promise is resolved as Rejected, it calls the reject method with an error message.

The “.catch” method manages the error situation; it is called to handle the error.

This applies to the executorFunction and the consumers that executes in the promise chain.

Each callback using the “.then” method with a return statement returns a new Promise object. This applies to the “.catch” method as well.

Internally, “.catch” invokes the “.then” method by passing the argument it receives as a second argument to the “.then” method. It possibly looks like the code snippet below…

This approach of segregating the program logic from error handling makes the code easier to understand

.finally()

The “.finally” method is called regardless of the state of a Promise. One of its uses is the cleanup activity. This method receives no argument and should not return anything. In case it does, the system ignores it.

Summary

In this article, we understood the concept of Promise and how it helps developers write readable and maintainable code.

Learned about different states of a Promise. How and when does the transition to different states happen.

Understood the use of .then, .catch, and .finally methods.

In the following article, I will share different scenarios where we can use the Promise.

References

--

--