loading...

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

puruvj profile image PuruVJ Originally published at puruvj.dev ・2 min read

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);

What would this output?

[Promise, Promise, Promise];

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)]);

will return

[
  { id: 1, ...otherData },
  { id: 2, ...otherData },
  { id: 3, ...otherData },
];

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);

That would output us the same object as above

[
  { id: 1, ...otherData },
  { id: 2, ...otherData },
  { id: 3, ...otherData },
];

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);

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))
);

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);
}

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.

Discussion

pic
Editor guide
 

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) ) )
);
 

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))
);
 

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 :)

 

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