Youssef Muhamad
Youssef Muhamad

Reputation: 139

async Array.map() inside another map call

I have a method that receives a profiles array and I have to map for each profile and inside this map I have to map again in the photos propriety, which contains the pictures ids for requesting to an API for getting this picture.

The question is, where can I safely access this profiles array with their loaded photos for each respective profile?

profiles.map((profile, i) => {
    let photos = []

    Promise.all(profile.photos.map(async idPhoto => { 
        const res = await fetch(...)
        const img = await res.blob()
        photos.push(img)
    }))

    .then(() => profiles[i].photos = [...photos])
})

Upvotes: 5

Views: 246

Answers (2)

macphilips
macphilips

Reputation: 557

I think it would be better to separate each mapping into its own function, it makes it easier to read. I refactored your code into this:

    let fetchPhoto = async (photoId) => {
        // const res = await fetch(...);
        // return res.blob();
        return { imageData: photoId } // mock blob result
    };

    let mapPhotoIdToImage = async (profile) => {
        let photos = profile.photos.map(fetchPhoto)
        photos = await Promise.all(photos);
        profile.photos = photos;
        return profile;
    };

    let profileList = [{photos: ['id1', 'id2']}];

    let result = await profileList.map(mapPhotoIdToImage);

result:

[{ photos: [ { imageData: 'id1' }, { imageData: 'id2' } ] }]

Upvotes: 1

rh16
rh16

Reputation: 1073

With the outer map function the way it currently is, the Promise.all() calls are discarded, so there is no way for your code to detect when they are complete.

However, since you also do not appear to be using the return value of the outer map, we can make it return an array of Promises that resolve when the inner their array of Promises is all resolved. And then we can use the same Promise.all(array.map()) pattern as we use for the inner map.

const photoRequests = profiles.map(async (profile, i) => {
      let photos = []

      await Promise.all(profile.photos.map(async idPhoto => { 

        const res = await fetch(...)
        const img = await res.blob()
        photos.push(img)
      }));

      profiles[i].photos = [...photos];
})

// And now...

await Promise.all(photoRequests); 
// After this it is safe to access.

// Or, if the outer map is not in an async method:

Promise.all(photoRequests).then(() => {
  // It is safe to access profiles here
});

I've refactored the outer map to be an async function (aids readability IMO), but you can put it back if you prefer. Just have the outer map function return the result of the Promise.all call.

As to what else could be improved here, the having variables photos and profile.photos is a little confusing, so consider renaming photos. Also make it const while you're at it, as it's never reassigned.

Unless there's some other code that manipulates the photos array, the array spread syntax is not needed. Same for the index variable. Final code might look something like:

const photoRequests = profiles.map(async profile => {
      const loadedPhotos = []

      await Promise.all(profile.photos.map(async idPhoto => { 

        const res = await fetch(...)
        const img = await res.blob()
        loadedPhotos.push(img)
      }));

      profile.photos = loadedPhotos;
})


await Promise.all(photoRequests); 

Or you could use the fact that Promise.all resolves to an array containing the resolve values of the individual promises it received:

const photoRequests = profiles.map(async profile => {

      profile.photos = await Promise.all(
        profile.photos.map(async idPhoto => { 

          const res = await fetch(...)
          return res.blob()

        })
      );
})


await Promise.all(photoRequests); 

Upvotes: 2

Related Questions