Using map() with async/await
Let’s start with the setup, which is very simple but it serves the purpose well.
We have an array of names in index.js
:
const names = ['Alice', 'Bob', 'Carol']
We also have a function in you-are-great.js
. which makes these people great (because they are):
function youAreGreat(name) {
return Promise.resolve(`You are great, ${ name }!`)
}
module.exports = youAreGreat
The task is to apply the youAreGreat
function to names
and log them to the console.
1. The classic
If iteration comes up as a topic, most people think of the for
or for..of
loops. Let’s quickly require youAreGreat.js
in index.js
:
// index.js
const youAreGreat = require('./you-are-great')
const names = ['Alice', 'Bob', 'Carol']
async function logInsideForOf(names) {
for (const name of names) {
const toLog = await youAreGreat(name)
console.log(toLog)
}
console.log('Done')
}
logInsideForOf(names).catch(e => console.error(e))
// You are great, Alice!
// You are great, Bob!
// You are great, Carol!
// Done
youAreGreat
returns a Promise
, so we need to await
it. await
only works inside async
functions, therefore we need to wrap the loop in a function called logInsideForOf
, and invoke this function.
If we don’t wrap await youAreGreat(name)
in an async
function, it won’t work:
// index.js
for (const name of names) {
const toLog = await youAreGreat(name)
console.log(toLog)
}
// SyntaxError: await is only valid in async function
But if we don’t await
it, we’ll get pending promises:
// index.js
for (const name of names) {
const toLog = youAreGreat(name)
console.log(toLog)
}
console.log('Done')
// Promise { <pending> }
// Promise { <pending> }
// Promise { <pending> }
// Done
None of them are good, so wrap it in an async
function.
2. The functional way
for..of
doesn’t suit the functional way of writing code. Let’s make it functional then.
2.1. forEach()
forEach
just seems to do the job. It applies a callback function on each element in the array (in our case, it’s names
), and this is exactly what we want.
// index.js
function logInsideForEach(names) {
names.forEach(async (name) => {
const toLog = await youAreGreat(name)
console.log(toLog)
})
console.log('All logged')
}
logInsideForEach(names)
// All logged
// You are great, Alice!
// You are great, Bob!
// You are great, Carol!
Oops. It’s not working as expected. Alice and her friends are still great, but they are only great after the line All logged
has been executed.
forEach
processes the greatness synchronously, and it doesn’t wait for the promise to resolve, which is an asynchronous process. Although it applies the async
callback function on each name, it all happens in the next cycle of the event loop.
2.2. map() with async/await
We can use map
with Promise.all
.
Promise.all
returns a promise and accepts an iterable object as an argument. Arrays are iterables, and because map
returns an array, we are good to go.
We can write the following syntax:
// index.js
async function logInsideMap(names) {
const [Alice, Bob, Carol] = await Promise.all(names.map(youAreGreat))
console.log(Alice)
console.log(Bob)
console.log(Carol)
console.log('All logged')
}
logInsideMap(names).catch(e => console.error(e))
// You are great, Alice!
// You are great, Bob!
// You are great, Carol!
// All logged
names.map
returns an array of promises, because the youAreGreat
itself returns a promise. For each name in names
, we’ll return youAreGreat
. The order of the names is kept, and we can use Array destructuring to access the values.
The above code could have been written differently, it all depends on the situation and the function we want to invoke on each element of the array.
If we don’t want to use destructuring, we can do something like this:
// index.js
async function logInsideMap() {
await Promise.all(names.map(async (name) => {
const toLog = await youAreGreat(name)
console.log(toLog)
}))
console.log('All logged')
}
logInsideMap(names).catch(e => console.error(e))
// You are great, Alice!
// You are great, Bob!
// You are great, Carol!
// All logged
Because nothing is returned from map
, we just await
youAreGreat
to resolve with each name, which means that the array map
returns will contain one single promise. If we want to log the All logged
message, we need to await
Promise.all
as well.
3. Conclusion
If we need to apply asynchronous code on multiple elements in an array, we can use the for..of
syntax or in the functional way, the map
method with Promise.all
.
Thanks for reading and see you next time.