DEV Community

Jordan Jaramillo
Jordan Jaramillo

Posted on

How to use async/await with .map in js

How to use async/await with .map in js

At some point you may have wondered how to use asynchronous functions in methods like .map or .forEach, because in this little blog you will see what the most common errors are and how to solve them.

For this we will have the following base code in the index.ts file:

const usernames: string[] = ["jordanrjdev", "anonymous123", "channelyy"];

const simulateFetchData = (username: string): Promise<string> => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(`${username} is a valid username`);
    }, 1000);
  });
}
Enter fullscreen mode Exit fullscreen mode

As you can see we have an array of usernames and a function that takes a parameter and returns a string.

Now we will iterate the array of usernames to obtain the simulated data of each user with the map method:

const dataUsers = usernames.map(async (username) => {
   return await simulateFetchData(username);
});
console.log(dataUsers);
Enter fullscreen mode Exit fullscreen mode

But when executing this we will see in the console the following result:

[ Promise { <pending> }, Promise { <pending> }, Promise { <pending> } ]
Enter fullscreen mode Exit fullscreen mode

So to solve it we have 2 options, which are to use Promise.all or use a for of

For of

We are going to use a for of to be able to solve this error that is very common and can make us lose a lot of time when trying to find the most appropriate solution.

const getWithForOf = async() => {
   console.time("for of");
   const data = []
   for (const username of usernames) {
     let dataUser = await simulateFetchData(username);
     data.push(dataUser);
   }
   console.timeEnd("for of");
}
getWithForOf();
Enter fullscreen mode Exit fullscreen mode

With this option the code will be executed sequentially, so you can wait for each call. This will help us get each iteration resolved before moving on to the next.

Keep in mind that if we do an N number of iterations and each of these takes 1 second to resolve, as in our example, it means that it will take a total of 3 seconds to finish executing this portion of code. We can see this in the console output thanks to console.time :

for of: 3,012s
Enter fullscreen mode Exit fullscreen mode

Promise.all

Now we will use the promise.all method to solve our problem, so we will have the following code:

const getWithPromiseAll = async() => {
   console.time("promise all");
   let data = await Promise.all(usernames.map(async (username) => {
     return await simulateFetchData(username);
   }))
   console.timeEnd("promise all");
}

getWithPromiseAll();
Enter fullscreen mode Exit fullscreen mode

As you can see we have a Promise.all method which receives an array of promises, remember that the

usernames.map(async (username) => {return await simulateFetchData(username);})

returns an array of promises, just what we need so we pass it to Promise.all to resolve them.

This method will cause all asynchronous code to be resolved in parallel.

So unlike the for of, let's see how long it takes to execute this function in the console:

promise all: 980.3000000119209ms
Enter fullscreen mode Exit fullscreen mode

So if we have a number N of asynchronous functions, they will be executed and resolved without waiting between them, which can come in handy in some specific case.

Sometimes for performance reasons we will need to execute our promises in parallel with Promise.all and other times we will need to do it in sequence with the for of loop. The important thing is that you understand the difference between each one and how to adapt it to your needs.

If you have any questions or suggestions do not forget to comment, see you soon :)

You can see the complete code in this codesandbox repository

Discussion (13)

Collapse
thuutri2710 profile image
Tris

Could you help me explain what happen if there is a fetch request is failed in using Promise.all?

Collapse
jordandev profile image
Jordan Jaramillo Author

yes, it is a good question I will give you an example that will solve your doubt.

Promise.all([
  Promise.resolve(20),
  Promise.reject("there was an error"),
  new Promise((resolve) => setTimeout(() => resolve(30), 300)),
])
  .then((values) => console.log(values))
  .catch((err) => console.error(err));


Promise.allSettled([
  Promise.resolve(20),
  Promise.reject("there was an error"),
  new Promise((resolve) => setTimeout(() => resolve(30), 300)),
]).then((values) => console.log(values));

Enter fullscreen mode Exit fullscreen mode

In the promise.all if a call fails then it will not enter the then but it will go directly to the catch.

But if you need it to return everything with its states you can use Promise.allSettled which will return an array with the status of each of the promises

script log

❯ node index.js
there was an error
[
   { status: 'fulfilled', value: 20 },
   { status: 'rejected', reason: 'there was an error' },
   { status: 'fulfilled', value: 30 }
]
Enter fullscreen mode Exit fullscreen mode
Collapse
thuutri2710 profile image
Tris

Agree with you @jordan Jaramillo. Btw, we can also use Promise.all to resolve this problem. Assume that we're fetching many requests. If one of them is successful, we'll return:

{
  value: 20,
  status: success
}
Enter fullscreen mode Exit fullscreen mode

Otherwise

{
 value: null,
 status: failed
}
Enter fullscreen mode Exit fullscreen mode

How about your thought?

Thread Thread
jordandev profile image
Jordan Jaramillo Author

The problem with promise.all is that you will not be able to access which ones were resolved correctly since it directly passes to the catch.

Thread Thread
thuutri2710 profile image
Tris

I mean that if any request is failed, we still use resolve instead of reject. We only change the returned status.

{
  value: null,
  status: failed,
}
Enter fullscreen mode Exit fullscreen mode
Thread Thread
jordandev profile image
Jordan Jaramillo Author

yes, you can do it that way, it's a good idea I hadn't thought about it but if you want to have it automatically you can use allSettled.

Thread Thread
thuutri2710 profile image
Tris

Agree !!!

Collapse
pankajgulabsharma profile image
pankajgulabsharma

I have one question when I will use Promise.all and for of loop with async-await 🤔

Collapse
jordandev profile image
Jordan Jaramillo Author

A case based on real work might be that you have an array of filenames and you want to get the data from each file asynchronously, in this case you would use Promise.all since the order doesn't matter and one doesn't depend on the other.

But what happens if in order to access the information of the next one you need to have the information of the previous one since they asked you to do so, then you must use for of since it is executed sequentially here.

Collapse
rishadomar profile image
Rishad Omar

To expand on your file fetching example. You would use the for...of if you wanted to concatenate a set of files. For example: concatenate f1.txt f2.txt.
The sequence must then be f1.txt followed by f2.txt.
If promise.all was used and f2.txt was a small file while f1.txt was a huge file, then the concatenation would have the incorrect sequence.
PS I haven't tried this but will.

Thread Thread
jordandev profile image
Jordan Jaramillo Author

Exact!

Collapse
harikanani profile image
HARIKRUSHN KANANI

It really helps a lot. Thanks for sharing and your efforts 🙌🙌.

Collapse
jordandev profile image
Jordan Jaramillo Author

Thank you so much 🙌🤝