Using async/await in a loop

I know there are a lot of similar questions out there already but none of them seem to solve this specific problem. I have a Javascript function that contains a forEach loop and inside the forEach loop, there are asynchronous calls.

The code roughly looks like this:

const handleConvert = () => {

    const refs = { ...imageRefs.current }

    // Outer loop
    Object.keys(refs).forEach(key => {
      
      // Inner loop
      refs[key].forEach(ref => {
        toPng(ref)
          .then((dataUrl) => {
            props.setImageUrls(old => {
              return {
                ..old,
                [key]: [...old[key], dataUrl]
              }
            })
          })
          .catch(err => {
            console.error(err)
          })
      })
    })
    setConverting(false)
    setConverted(true)
  }

refs is an object that contains 2 keys and each key has an array for it’s value. It looks something like this:

{
  a: [1, 2, 3],
  b: [3, 4, 5]
}

The toPng() function returns a promise. I am trying to wait for all of those promises to resolve before executing the code after the outer forEach loop:

if (mounted) {
  setConverting(false)
  setConverted(true)
}

Is there a way to wait for all the promises to resolve before executing the code after the outer loop? Maybe with Promise.all()?

Answer

Nest another Promise.all to wait for each subarray to finish. Make sure to map and return every Promise to the caller so the Promise.all functions properly.

Promise.all(
    Object.entries(refs)
        .map(([key, arr]) => Promise.all(
            arr.map(num =>
                toPng(num).then(dataUrl => {
                    props.setImageUrls(old => ({ ...old, [key]: [...old[key], dataUrl] }))
                })
            )
        ))
)
    .then( // all done