From Callback Hell to Promise Chains
Yurich
Posted on April 7, 2023
When programming in JavaScript, we often have to make requests to the server. And as we know, the JS machine does not wait for the request to be done and continues to execute the next code. This approach in JS is called asynchrony.
All requests to the server are promises. Although they do not return any result at the same time, they promise that they will return something, or they will throw an error.
I want to consider an example of when we need to make several requests to the server or call several promises. Let's create an array of promises.
const promises = [
Promise.resolve(1),
...
Promise.resolve(n)
]
I just want to call all these promises from a regular loop
for (const promise of promises) {
promise.then(r => console.log(r))
}
And it will work if we don't have to wait for the result. But most likely we need the data that returns the promises. A special case may be when each subsequent promise must depend on the data of the previous one.
The first thing that comes to mind is to call each subsequent promise in the .then
method of the previous one.
promises[0].then(result => {
console.log(result)
promises[1].then(result => {
console.log(result)
promises[2].then(result => {
console.log(result)
...
})
})
})
If you continue to nest promises one inside the other, then such code will be very difficult to read. And only indents help to orientate a little. This approach creates Callback Hell.
In this case, it would be better if the promise returns another promise. So this code can be reformatted in the promise chain.
promises[0]
.then(result => {
console.log(result)
return promises[1]
})
.then(result => {
console.log(result)
return promises[2]
})
...
.then(result => {
console.log(result)
})
This approach is much more readable and resembles the Chain of Responsibilities design pattern.
But when working with arrays, it is still easier and clearer to use loops. Let's do it. To do this, we need to create a separate function and use the async/await
syntax.
const run = async () => {
for (const promise of promises) {
const result = await promise
console.log(result)
}
}
run()
This is much shorter and clearer. This approach will work in most cases and will be understandable to a developer of any level.
The fact is that all promises are executed synchronously one after the other and it takes more time than if all requests were executed simultaneously. And in the case when the next promise does not depend on the previous one, and we want to work with the results of all requests, it is better to use special methods all, allSettled, race, any
Let's redesign this functionality so that it works more efficiently.
Promise.all(promises).then((responses) => {
for (const response of responses) {
console.log(response);
}
})
.catch(error => console.error(error))
This is much better, and all promises inside Promise.all
are executed asynchronously.
We have considered several approaches to working with the array of promises and their main pros and cons.
I hope this article helps you better understand promises and write better code.
Posted on April 7, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.