DEV Community

Cover image for Handling async operations and promise values in javascript arrays
Nwakwoke Patrick Nnaemeka
Nwakwoke Patrick Nnaemeka

Posted on • Updated on

Handling async operations and promise values in javascript arrays

ES6 brought a lot of notable goodies to javascript and one of the famous ones are async/await and popular array methods like map, filter, reduce and foreach. These operations all take a callback and have their various ideal use cases but whichever one you use, try not to sacrifice code legibility.

To the main reason we are here, I have often had difficulties in the past using these array methods whenever my callback had to be an async function or handle a Promise. I eventually got to figure it out and put this piece together in case anyone else faces these struggles. I will give examples using the map and reduce methods. The knowledge from both can be applied to the other methods which are quite similar.

MAP

I use the map operation when I need to return an identical array but with each value going through the same modification. Let's assume we have a function 'findById' that takes a data's id and returns the corresponding data to that id. If we have an array of ids and we want to get the corresponding data for each of these ids, we could easily do

const getData = (arrayOfIds) => {
 const arrayOfData = arrayOfIds.map(id => findById(id));
 console.log(arrayOfData);
}

arrayOfData should contain our data right? Well, that depends. If findById is a synchronous function, then yes. If it an async function, what we get in our console is an empty array. Why? javascript event loop drama. The thread won't wait for the completion of the findById operations before moving to the next operation.

Now let us treat findById as an async function and fix our code to ensure our promise is resolved before running any operations that depend on our resolved data.

 const getData = (arrayOfIds) => {
  Promise.all(arrayOfIds.map(id => findById(id)))
   .then((arrayOfData) => {
     console.log(arrayOfData);

     //do what you want with data
  });
 }

We should now see our data instead of an empty array in the console. Promise.all is used to resolve an array of Promises. findById will return a Promise, so our map operation will return an array of Promises, hence the need for Promise.all. This solution, however, can be even neater if getData is an async function, so we can resolve our promise with a simple await

 const getData = async (arrayOfIds) => {
  const arrayOfData = await Promise.all(
    arrayOfIds.map(id => findById(id))
  );

  console.log(arrayOfData);

  // do what you want with data
 }

Easy peasy!

Reduce

I use reduce when I want to fluently aggregate my array into a single value. For our reduce example, we will be trying to convert an array of ids into a javascript Object. Let us will assume our findById method returns object data that contains a name field, so we want to convert our array of ids to an object with each data's id as a key and the name as corresponding value. Something that looks like this

 {
  53b55aea-32f8-47fe-aa36-9fd44f6c399f: 'Alec Benjamin',
  53b55aea-32f8-47ff-aa37-9fdhjdj5678f: 'Chester Bennington'
 }

Here is how we can implement this

const getUserDataInDictFormat = async (arrayOfIds) => {
  const userData = await arrayOfIds.reduce(async (prevAggregate, currentId) => {
    const currentUserData = await findById(currentId);

    const aggregate = await prevAggregate;

    aggregate[currentId] = currentUserData.name;

    return aggregate;
  }, {})
 }

 console.log(userData);

 //do something with userData
}

Since our callback returns a Promise on each iteration, our prevAggregate will need to be awaited to get its value. The final value of the reduce operation will also be a Promise, which we also await to get its resolved value.

The secret for me is, try as much as possible to figure out which values will be a promise and ensure they are resolved before using them again.
I hope this was helpful and feel free to drop a comment if you have any concerns.

Top comments (0)