DEV Community

PuruVJ
PuruVJ

Posted on • Originally published at puruvj.dev

Async Await usage and pitfalls in Array.prototype.map() and chaining

Originally published @ puruvj.dev

Let's consider the code below

const IDs = [1, 2, 3];

const usersData = IDs.map(async (id) => await getUserData(id));

console.log(usersData);
Enter fullscreen mode Exit fullscreen mode

What would this output?

[Promise, Promise, Promise];
Enter fullscreen mode Exit fullscreen mode

All these are promises. But we are expecting data as objects(or whatever other format you can think of) here. How do await every single array item?

The solution here is Promise.all. Quick recap:

Promise.all takes in an array of promises, runs them concurrently until they all resolve, and return a bigger promise with the outcomes from those promises as resolved values as an Array

For example

await Promise.all([getUserData(1), getUserData(2), getUserData(3)]);
Enter fullscreen mode Exit fullscreen mode

will return

[
  { id: 1, ...otherData },
  { id: 2, ...otherData },
  { id: 3, ...otherData },
];
Enter fullscreen mode Exit fullscreen mode

If you think about it, the code snippet where we're mapping over IDs is just an Array of Promises. We can directly Promise.all that array

const IDs = [1, 2, 3];

const usersDataPromises = IDs.map(async (id) => await getUserData(id));

const usersData = await Promise.all(usersDataPromises);

console.log(usersData);
Enter fullscreen mode Exit fullscreen mode

That would output us the same object as above

[
  { id: 1, ...otherData },
  { id: 2, ...otherData },
  { id: 3, ...otherData },
];
Enter fullscreen mode Exit fullscreen mode

Tricky part

The trick above works like a charm. However, difficulty arises when you chain another array method to the existing array, like this

const IDs = [1, 2, 3];

const usersDataPromise = IDs.map(async (id) => await getUserData(id)).map(
  async (data) => await getPosts(data)
);

const usersData = Promise.all(usersDataPromise);

console.log(usersData);
Enter fullscreen mode Exit fullscreen mode

It will return an error. Why?

Promise.all tries to run all promises at once. And I mean, All of them. It will try to run the 2nd map alongside the first map. You can see for yourself this is a problem, as the second map depends on the value from the first.

How do we resolve this (Pun intended 😎)?

Solutions

There can be many ways to solve this problem. I will share 2 here

1st

Promise.all at every single step

const IDs = [1, 2, 3];

const usersData = await Promise.all(
  IDs.map(async (id) => await getUserData(id))
);

const usersPosts = await Promise.all(
  usersData.map(async (userData) => await getPosts(userData))
);
Enter fullscreen mode Exit fullscreen mode

2nd

A plain old for of loop:

const IDs = [1, 2, 3];

const usersPosts = [];

for (let id of IDs) {
  const userData = await getUsersData(id);

  const userPosts = await getPosts(userData);

  usersPosts.push(userPosts);
}
Enter fullscreen mode Exit fullscreen mode

I prefer the 2nd approach. If you wanna add an extra step, you simply add an extra line, whereas the 2st will require a whole extra Promise.all(array map), which ultimately is just code redundancy.

Top comments (4)

Collapse
 
stexx profile image
Stefan Breitenstein • Edited

at least you can rid of some awaits:

const IDs = [1, 2, 3];

const usersData = await Promise.all(
  IDs.map(async (id) => await getUserData(id))
);

const usersPosts = await Promise.all(
  usersData.map(async (userData) => await getPosts(userData))
);
const IDs = [1, 2, 3];

const usersData = await Promise.all(
  IDs.map(async (id) => getUserData(id))
);

const usersPosts = await Promise.all(
  usersData.map(async (userData) => getPosts(userData))
);

You could also use .then here:

const IDs = [1, 2, 3];

const usersPosts = await Promise.all(
  IDs.map(async (id) => getUserData(id).then((userdata) => getPosts(userData) ) )
);
Collapse
 
puruvj profile image
PuruVJ

Thanks for the suggestion. I left in all those extra async awaits to show these functions return promises. Otherwise, I simply remove async and await if the function returns a promise.

const usersData = await Promise.all(
  IDs.map((id) => getUserData(id))
);
Collapse
 
stexx profile image
Stefan Breitenstein

Sure but I talk about the explicit the await, not the async part.

And here is a nice part of async functions:
They results always in a Promise, so you don't have to care about the return statement.
At least with the Promise/await part.

So what i mean

const usersData = await Promise.all(
  IDs.map(async (id) => getUserData(id)) // returns always a promise, no gain to write also await.
);

There is even a eslint rule for that:
eslint.org/docs/rules/no-return-await

I think they explain the benefits and tradeoffs better than me :)

Collapse
 
puruvj profile image
PuruVJ

.then method looks interesting. I will have to try that too