ffx292
ffx292

Reputation: 701

How can I wait for multiple Promise.all()

I'm working with some old code and I'm trying to return results for two arrays of promises.

So basically body contains first and second which are arrays of ids. I know that the below code works for a small array of of 1 to 5 ids, but if I were to do a few hundred, would the resolve fire before I get the hundred of results from both promise arrays?

 const doSomething = (body) => {
    return new Promise((resolve, reject) => {
      const promisesOne = body.first.map((id) => 
        doSomethingOne(id)
      );

      const promisesTwo = body.second.map((id) => 
        doSomethingTwo(id)
      );

      let response = {
        first: {}
        second: {}
        headers: 'mock headers'
      };

      Promise.all(promisesOne).then((results) => {
        response.first = results;
      });

      Promise.all(promisesTwo).then((results) => {
        response.second = results;
      });

      resolve(response);
  });
};

Also I won't be able to refactor this to async/await as this codebase does not use it.

Upvotes: 3

Views: 2610

Answers (2)

Phil
Phil

Reputation: 164723

would the resolve fire before I get the ... results from both promise arrays?

Yes, absolutely because you aren't waiting for them to resolve.

Simply wrap the two Promise.all() promises in another Promise.all() to wait for everything then create the final object you want.

const doSomething = (body) => {
  return Promise.all([
    Promise.all(body.first.map(doSomethingOne)),
    Promise.all(body.second.map(doSomethingTwo)),
  ]).then(([first, second]) => ({
    first,
    second,
    headers: "mock headers",
  }));
};

You could make this generic by creating a map of body property names to their corresponding mapping function. For example

const mappers = {
  first: doSomethingOne,
  second: doSomethingTwo,
};

const doSomething = (body) => {
  return Promise.all(
    Object.entries(mappers).map(([key, mapper]) =>
      Promise.all(body[key].map(mapper)).then((data) => ({ [key]: data }))
    )
  )
  .then((results) => Object.assign({ headers: "mock headers" }, ...results));
};

Upvotes: 5

CertainPerformance
CertainPerformance

Reputation: 370679

Your code will not work at the moment - the resolve(response) will run before either of the Promise.alls are done.

First, refactor your .first and .second (etc) properties so that they're arrays, rather than unique string properties. Then, it'll be easy to .map them with two Promise.alls - one to wait for an individual array to fulfill, and an outer one to wait for all arrays to fulfill.

The same thing should be done for the doSomethingOne etc functions - to make the code easy to work with, use an array of functions instead of having many standalone identifiers.

Also, there's no need for the explicit Promise construction antipattern.

const doSomethings = [
  doSomethingOne, // define these functions here
  doSomethingTwo,
];

const doSomething = (body) => Promise.all(
  body.arrayOfArraysOfIds.map(
    (arr, i) => Promise.all(
      arr.map(id => doSomethings[i](id))
    )
  )
)
  .then((results) => ({
    headers: 'mock headers',
    results,
  });

Upvotes: 2

Related Questions