Use async/await with map method without failing in the process
- JavaScript
In the first example that will be provided, the use of await keyword inside the map method is redundant and unnecessary, and it was just used to better illustrate the main idea of this article. For more information about this, you can check Eslint docsopens a new window as this fact is very well explained.
Have you ever tried using map method and async/await to iterate over an array of promises hoping that the async operations will be awaited on each iteration?
Using map method
Let's consider the following example:
// Promises const firstPromise = () => Promise.resolve(1) const secondPromise = () => { return new Promise(resolve => { setTimeout(() => { resolve('Hello') }, 5000) }) } // Array of promises const promisesArray = [firstPromise, secondPromise] const resolvedPromises = promisesArray.map(async (promise) => { return await promise() }) console.log(resolvedPromises) // Expectation: [ 1, 'Hello' ] // Real output: [ Promise { <pending> }, Promise { <pending> } ]
As you can see, the output is not quite what we expected, we are getting an array of pending promises instead of the resolved values of those promises. What happened?
In JavaScript, there are synchronous and asynchronous operations. Synchronous operations, as its name suggests, are those that are executed following a sequence, i.e. every statement of the code gets executed one by one. So, basically a statement has to wait for the earlier statement to get executed. Asynchronous operations on the other hand don't follow this pattern, they won't block the execution of the remaining code.
As you may know, the map method is a clear example of a synchronous operation, it will block further code execution until all iterations have been completed. Promises on the other hand, are asynchronous.
Array methods like forEach, map, etc are not promise or async-aware. In other words, they will synchronously execute the "mapper" callback function regardless of the await keywords used inside them, and then move on to the next element in the array. This means that the promises won't be awaited as one would expect, and as a result, you will get an array of promises (in the case of map).
Promise.all and Promise.allSettled
You can use Promise.all or Promise.allSettled depending on your needs.
Both methods are used to handle multiple promises and they both receive an array of promises as an input. The key differences between them are their respective outputs and how they handle rejected promises.
Using Promise.all if at least one of the promises in the array is rejected, then it will reject immediately. If all input promises have been resolved, it will return a single promise that resolves to an array with the result values of the input promises.
Promise.allSettled on the other hand behaves differently, it doesn't care if one or all of the input promises have been rejected. It returns also a single promise that fulfills when all of the given input promises have completed (resolved or rejected). Regarding the output, it returns an array of objects representing each one of the completed promises. Each object has a status key which has a string value that indicates if the promise has been resolved or rejected. If the promise has been resolved, then it will contain a value key with the promise's resolved value. If it has been rejected, then it will contain a reason key with a string as a value that indicates why the promise has been rejected.
That being explained, for this article, I will be using Promise.all as example:
const resolvePromises = () => { return Promise.all(promisesArray.map(promise => { return promise() })) } resolvePromises().then(results => console.log(results)) // Output: [ 1, 'Hello' ]
Now we got the output that we were expecting, but what if we need to make these async operations sequentially?
Using a for…of loop
// Sequentially const resolvePromisesSequentially = async () => { const resolvedPromises = [] for (promise of promisesArray) { const result = await promise() resolvedPromises.push(result) } return resolvedPromises } resolvePromisesSequentially().then(results => console.log(results)) // Output: [ 1, 'Hello' ]
That's all for this article, hope you found it useful. See you in the next one.