Callbacks in JavaScript — Part 3

Sunilkathuria
In Computing World
Published in
5 min readApr 19, 2024

In the last article, we learned about the concept of Promise and how we can implement it to manage asynchronous code easily. This article, last part of the series, extends the previous one and continues to learn a few more things about Promise. After this, we will learn about async/await, which is implemented on top of Promise and goes a step further in the readability and maintainability of code.

Promise a quick recap

Promise offers a cleaner way to manage asynchronous operations. It is an object that represents the completion or failure of an asynchronous operation.

The Promise constructor accepts a callback function (executorFunction) as an argument for the asynchronous operation.

The Promise connects two program parts: the excutorFunction and the part of the code that depends on the excutorFunction called 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.

A few more promises

When consumers are in sequence (promise chaining)

In this situation, once a Promise object is created, all its subsequent operations (consumers) are executed sequentially. Each is doing its part.

In the following example, a Promise promise resolves to an initial value, and subsequent consumers do additional calculations on top of the initial value and pass the updated value to the following consumer in the sequence.

Output

Following is the graphical representation of the sequence of execution. It is also known as promise chaining.

Consumers working on updated value/data

You must have noticed in the code above that each consumer has a return statement after performing its operation (calculation). This return statement ensures that the next consumer in the sequence gets an updated value. It creates a new Promise object that resolves the calculated value. If the return statement is not there, the subsequent consumers will have no value to work on.

More than one consumer of a promise in parallel

For a Promise, more than one consumer can work in parallel. In this case, all the consumers will get the same resolved value of the Promise to work on. The code will look like the below snippet.

When consumers are asynchronous

The Promise object does not execute the asynchronous code alone. Subsequent consumers may also execute asynchronous code. When this happens, the consumer generally explicitly creates a new Promise object and resumes the execution sequence. You must have noticed this (example above) when reading three files asynchronously shared above.

Managing promises

We have seen the example of reading three files asynchronously, one after the other, and finally writing the content of all three files in another file.

Promise.all

Now, we change the sequence of this execution. First, we read all three files. Once all the files are read successfully, we write the data to a new one. For this, we will use the method “Promis.all().” This method accepts an array of “Promise” objects, the three files we read asynchronously. Once all the promises are resolved, the consumer code starts its execution. If any of the promises fail, then all will fail. The following code depicts the same.

Promise.any

In this case, if any of the Promise objects passed to the “.any” method resolves, the overall Promise object resolves. When all the Promise objects passed to the “.any” method reject, the overall Promise object rejects, too.

In this case, the data/value of the first Promise object that resolves is passed to the consumers.

Promise.race

In this case, all the Promise objects passed to the “.race” method race against each other. If the first resolved object resolves, the overall Promise object resolves. When the first resolved object is rejected, the overall Promise object is rejected, too.

In this case, the data/value of the first resolved Promise object is passed on to the consumers.

async/await

async/await work on top of promises to write asynchronous code that looks synchronous. This makes the code easy to read and manage.

async

This keyword is used before defining a function. This indicates that the function will always return a Promise. It is also capable of managing asynchronous code execution.

await

This keyword is used only in an async function. This function waits for a Promise to get resolved. Either resolve or reject. In other words, it waits for a function to complete.

JavaScript will throw an error if you try to use await outside an async function.

If any statements within the async function throw an error, we can catch that error using the “.catch” method.

Following is the implementation of the example of reading three files asynchronously and showing the data of all three files.

Summary

Promises: Represent the eventual outcome (success or failure) of asynchronous operations and their resulting value.

Chaining: Links multiple asynchronous operations for a readable flow, passing results between them.

States of Promises: A promise can be pending (in progress), fulfilled (succeeded), or rejected (failed).

Managing Promises: Techniques like “.then()” and “.catch()” handle promise results and errors.

async/await: Simplifies promise-based code, making asynchronous code resemble synchronous code.

References

--

--