DEV Community

Nya
Nya

Posted on • Edited on

Array.map() + async/await

Meow

MDN Web Docs: The map() method creates a new array with the results of calling a provided function on every element in the calling array.

Today I found myself in a situation where I had to use an asynchronous function inside of an Array.map. Needless to say, Array.map is a synchronous function. Therefore, I had a slight problem in my hands.

I came across this problem while refactoring my code to get rid of a nasty “for..of”. Basically, I had an Array of objects upon which I had to make several calculations. One of these calculations involved making a call to an API which, of course, is asynchronous. Before my refactoring, I had no problem with using async/await inside my for..of loop. However, after making the switch to Array.map, I realized it wouldn’t be as simple.

Here’s what I did:

Asynchronous Array.map()

As you can see, my getDistance function simulates a call to an API, and returns a Promise.

Note: The return value of async functions is always a Promise

This means that our Array.map function will return an Array of unresolved promises. Therefore, outside of the Array.map, we have to wait for all the promises to be resolved before we can make use of the resulting Array. This is why we use Promise.all().

Note: If you aren’t comfortable with async/await syntax, you can always resort to good ol’ “.then”

You can check out and play with my solution in the following CodePen:

Pretty simple, right? It took me a while to figure it out…
Which is why I decided to share my solution, in case someone else encounters this issue. I hope it helps! Thank you for reading, feel free to share or leave a comment :)

P.S. You can check out my Github and Twitter pages if you like...

Top comments (10)

Collapse
 
havespacesuit profile image
Eric Sundquist

Using Promise.all(items.map(async (item) => asyncFunc(item)) looks pretty, but if you have thousands of items, it can eat a lot of your browser's resources managing all those promises. If resource consumption is a concern, there is nothing wrong with for (const item of items) { await asyncFunc(item) }.

Collapse
 
melaniecarr23 profile image
Melanie

I tried this using HttpClient and a data service for my angular project, and it returns the mapped object, but the value of that http function is an observable object. I've no clue what I'm doing wrong to not get the right value.

Collapse
 
nyagarcia profile image
Nya

The Angular HttpClient is designed to work with Observables (data streams), and this approach is Promise-based. You have two options:

  1. Work with Observables, where you have to subscribe to the Observable object in order to access its data. You can find more information here

  2. You can you use the toPromise() utility, which will transform the Observable you are receiving to a Promise, which you can then handle in the traditional way. More info about toPromise here

I hope this helps you out. Let me know :)

Collapse
 
midblue profile image
Jasper Stephenson

I use dev.to all the time, but this is the first time I've come here from googling a bug. Thanks for posting, and it fixed my issue!

Turns out you can't just do

async () => {
  const a = array.map(async el => await foo(el))
  await Promise.all(a)
  console.log(a) // this shows all values properly, but still wrapped in Promises
}
Collapse
 
mwalol profile image
mwalol

Thank you, i was having a hard time to solve this

Collapse
 
rubencfu profile image
Rubén Cruz

10/10

Collapse
 
ooloth profile image
Michael Uloth

Thank you! This got me out of a jam today.

Collapse
 
nomangul profile image
Noman Gul

Thanks @nyagarcia 🤓

Collapse
 
kpennell profile image
Kyle

I'm sorry but this is quite hard to understand, especially without context on what you're actually doing or trying to do here.

Collapse
 
jeffml profile image
Jeff Lowery

Note: The return value of async functions is always a Promise

I guess this is something I hadn't fully grokked. You're not returning a Promise in the async function, yet it returns a Promise!