How to use Async and Await with Array.prototype.map()
By Flavio Copes
Learn how to run an async function inside Array.prototype.map() and wait for every result by wrapping the returned promises in Promise.all() the right way.
You want to execute an async function inside a map() call, to perform an operation on every element of the array, and get the results back.
The answer: wrap the map() call in Promise.all(), and await that.
This is the correct syntax:
const list = [1, 2, 3, 4, 5] //...an array filled with values
const functionThatReturnsAPromise = item => { //a function that returns a promise
return Promise.resolve('ok')
}
const doSomethingAsync = async item => {
return functionThatReturnsAPromise(item)
}
const getData = async () => {
return Promise.all(list.map(item => doSomethingAsync(item)))
}
getData().then(data => {
console.log(data)
})
The main thing to notice is the use of Promise.all(), which resolves when all its promises are resolved.
Remember, we must wrap any code that calls await in an async function.
See the promises article for more on promises, and the async/await guide.
Why doesn’t await inside map() just work?
map() knows nothing about promises. It calls your function on each item and collects the return values.
An async function always returns a promise. So list.map(doSomethingAsync) doesn’t give you an array of results, it gives you an array of pending promises:
const result = list.map(item => doSomethingAsync(item))
console.log(result) //[ Promise, Promise, Promise, Promise, Promise ]
Promise.all() takes that array and returns a single promise, which resolves to the array of values once every promise in the list has resolved. That’s the missing piece.
Note that all the operations run in parallel. If one of them rejects, Promise.all() rejects immediately with that error.
If you need the operations to run one after the other, don’t use map() at all. Use a plain for...of loop, where await pauses each iteration:
const results = []
for (const item of list) {
results.push(await doSomethingAsync(item))
}
A real example
It can be difficult to visualize the example with those placeholder function names, so a simple example of how to use this technique is this Prisma data deletion function I wrote for a Twitter clone to first delete tweets and then users:
export const clearData = async (prisma) => {
const users = await prisma.user.findMany({})
const tweets = await prisma.tweet.findMany({})
const deleteUser = async (user) => {
return await prisma.user.delete({
where: { id: user.id }
})
}
const deleteTweet = async (tweet) => {
return await prisma.tweet.delete({
where: { id: tweet.id }
})
}
const deleteTweets = async () => {
return Promise.all(tweets.map((tweet) => deleteTweet(tweet)))
}
const deleteUsers = async () => {
return Promise.all(users.map((user) => deleteUser(user)))
}
deleteTweets().then(() => {
deleteUsers()
})
}
Technically this could be much easier summarized as
export const clearData = async (prisma) => {
await prisma.tweet.deleteMany({})
await prisma.user.deleteMany({})
}
but the above code is also valid, and shows how to use promises in Array.map(), which is the point of this tutorial.
Watch out for forEach()
A common mistake is trying the same trick with forEach():
list.forEach(async (item) => {
await doSomethingAsync(item)
})
console.log('done') //prints before the work is done!
forEach() discards the return values, so there’s nothing to await. The loop “finishes” immediately and your code moves on while the async work is still running.
If you need to wait for the results, use map() with Promise.all() as shown above, or a for...of loop.
Related posts about js: