Using async/await with .map()
⊷ 3 min readUsing an async function when mapping over an array seems to be a common point of confusion for developers whose main language isn't JavaScript. It usually stems from misunderstanding what async functions are or how Array.prototype.map()
works.
What are async functions?
Async/await is syntactic sugar over ES6 promises. It provides us with an easy way to order asynchronous calls sequentially, instead of chaining multiple .then()
calls, and makes asynchronous code look like synchronous code.
One thing that you should remember when working with async functions is that when called, they immediately return a promise. For example:
async function myFn() {
const response = await fetch("https://my-api.com/endpoint");
const data = await response.json();
console.log(data);
return 123;
}
const foo = myFn();
The constant foo
now holds a promise for the value 123
. It does not hold the value itself.
The above code is functionally equivalent to:
const foo = fetch("https://my-api.com/endpoint")
.then(response => response.json())
.then(data => {
console.log(data);
return 123;
});
What does .map() do?
Put simply, it takes a function, applies it to all the elements of the array, and creates a new array with the return values. For example:
const numbers = [1, 2, 3];
const incrementedNumbers = numbers.map(n => n + 1);
console.log(numbers); // outputs [1, 2, 3]
console.log(incrementedNumbers); // outputs [2, 3, 4]
Putting the two together
Let's say we have an array of product IDs and we want to fetch the products with those IDs to get an array of products.
async function main() {
const productIds = ["product1", "product2", "product3"];
// start all three fetch requests concurrently
const productPromises = productIds.map(async productId => {
const response = await fetch(`https://my-api.com/product/${productId}`);
const product = await response.json();
return product;
});
// get the resolved values in order
const products = await Promise.all(productPromises);
}
There are a few things to note here:
productPromises
holds an array of promises for product objects, not an array of product objects.
Remember, we passed an async function to our.map()
, and async functions always return a promise..map()
doesn't wait for the promise for product1 to resolve before going to product2 or product3.
Instead it calls the function, the function immediately returns a promise, and then the map immediately proceeds to the next element.
As such, there is no guarantee which product will be fetched first, since all three requests are fired without waiting for the previous one to finish, and the products are now being fetched concurrently. We are still getting the resolved values ordered properly thanks to awaiting Promise.all()
.
If we want the products to be fetched sequentially, firing each request only after the previous one has finished, we should use a for...of
loop instead:
async function getProduct(productId) {
const response = await fetch(`https://my-api.com/product/${productId}`);
const product = await response.json();
return product;
}
async function main() {
const productIds = ["product1", "product2", "product3"];
const products = [];
for (const productId of productIds) {
const product = await getProduct(productId);
products.push(product);
}
}
However, this should only be used when we absolutely need one async action to begin after the previous has finished.
If all we care about is the order of the resolved values, we should use the original approach. We start the requests concurrently, and as such the third request may finish before the first or second, but in the end we still get the values in order. Here's an alternative to the original example, that achieves the same thing by using a for...of
loop instead of awaiting Promise.all()
.
async function getProduct(productId) {
const response = await fetch(`https://my-api.com/product/${productId}`);
const product = await response.json();
return product;
}
async function main() {
const productIds = ["product1", "product2", "product3"];
const products = [];
// start all three fetch requests concurrently
const productPromises = productIds.map(getProduct);
// get the resolved values in order
for (const productPromise of productPromises) {
const product = await productPromise;
products.push(product);
}
}
That's all for today.