3

I know promise.all() expects an array of promises.

But, is it possible to do something like below? If no, please suggest a workaround.

It's not recommended to use await inside for loop. That's why I am pushing in an array and doing promise.all() on that.

var functionArray = [];
for (let i = 0; i < jobs.length; i += 1) {
  ...
  if (params.origins !== '' && params.destinations !== '') {
    functionArray.push(async function() {
      response = await getDistance(params.origins, params.destinations);
      if (response.error) {
        // handle error
        return null
      } else {
        distances = response.data.rows[0].elements.map((el, index) => {
          el.emp_id = empIdOrder[index];
          return el;
        });
        sortedDistances = sortDistance(distances);
        return formatDataForInsert(jobs[i].job_id, sortedDistances);
      }
    });
  }
}
var dataToBeinserted = await Promise.all(functionArray); // return an array with results

It doesn't work as expected.

await Promise.all(functionArray); always return [ [AsyncFunction], [AsyncFunction] ].

Shouldn't it be resolved instead?

sujeet
  • 3,480
  • 3
  • 28
  • 60
  • 1
    _"It's not recommended to use await inside for loop"_ got a reference for that? – Phil Apr 21 '20 at 06:57
  • 1
    @Phil `jshint` and `eslint` both suggests. https://eslint.org/docs/rules/no-await-in-loop – sujeet Apr 21 '20 at 07:05
  • Fair enough, thanks for clarifying. I only ask as using `await` within a `for` loop does actually work (as opposed to within a `forEach` callback). Of course, as the link says, its sequential and loses out on potential parallel operations – Phil Apr 21 '20 at 07:06

3 Answers3

4

The first problem is that Promise.all accepts an array of promises, not an array of functions - your current code won't work.

The main issue is that you're only conditionally using the result of the asynchronous operation. You can chain .then onto a Promise to make the Promise resolve to the result of the .then, rather than its initial resolve value. That is:

Promise.resolve(2)
  .then(res => res + 4)

results in a Promise that resolves to 6.

Using this logic, you can push a Promise to the array which, in its then, conditionally works with the result (distances = response.data...) and returns the final value, or doesn't return anything. At the end, call Promise.all on the array of Promises, and filter by boolean:

const promises = [];
for (let i = 0; i < jobs.length; i += 1) {
  if (params.origins !== '' && params.destinations !== '') {
    promises.push(
      getDistance(params.origins, params.destinations)
        .then((response) => {
          if (response.error) {
            // handle error
            return null
          } else {
            const distances = response.data.rows[0].elements.map((el, index) => {
              el.emp_id = empIdOrder[index];
              return el;
            });
            const sortedDistances = sortDistance(distances);
            return formatDataForInsert(jobs[i].job_id, sortedDistances);
          }
      })
    );
    }
}
const results = await Promise.all(promises)
  .filter(Boolean); // filter out failures

var dataToBeinserted = await Promise.all(functionArray); // return an array with results
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • 1
    It it rejects, the `Promise.all` will reject. If it may reject, and you want to just ignore the rejection but process those which succeed, use [`Promise.allSettled`](https://stackoverflow.com/a/56255129) and filter out the promises which rejected. – CertainPerformance Apr 21 '20 at 07:17
  • `formatDataForInsert(jobs[i].job_id, sortedDistances);` returns `[ {...}, {....}...]`; `result` should be `[{..}, {...}..]` not `[[{...}, {..}..], [{...}, {...}..]...]` Can I spread it, somehow? – sujeet Apr 21 '20 at 07:25
  • 1
    You can flatten it afterwards: `.filter(Boolean).flat()` – CertainPerformance Apr 21 '20 at 08:00
  • Had to wrap in parenthesis `(await Promise.all(promises)).filter(Boolean).flat();` , otherwise getting error `Promise.all(...).filter is not a function`. – sujeet Apr 21 '20 at 08:36
  • Oh right, need the `await` to resolve the `Promise.all` first – CertainPerformance Apr 21 '20 at 08:38
0

The function in your example is never executed, in order for them to resolve you can do like this (wrap it in parentheses and call right away):

functionArray.push((async function() {
  response = await getDistance(params.origins, params.destinations);
  if (response.error) {
    // handle error
    return null
  } else {
    distances = response.data.rows[0].elements.map((el, index) => {
      el.emp_id = empIdOrder[index];
      return el;
    });
    sortedDistances = sortDistance(distances);
    return formatDataForInsert(jobs[i].job_id, sortedDistances);
  }
})());

Or:

Promise.all(fnArray.map(f => f())
eosterberg
  • 1,422
  • 11
  • 11
  • Can you explain? Does `Promise.all()` runs anything passed parallel or it works that way only with promises? It clearly says it expects promises https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all . – sujeet Apr 21 '20 at 07:02
  • @Sujeet: The example in your link suggests otherwise: `const promise2 = 42;` – Rickard Elimää Apr 21 '20 at 07:05
  • Yes, I got to know this now _when the iterable contains no promises or when the iterable contains promises that have been fulfilled and non-promises that have been returned_ reference is the same as above. – sujeet Apr 21 '20 at 07:29
  • 1
    So, yeah we can pass a function as well, and promise will be resolved if it returns successfully or will be rejected if that function has caught an error inside it using try/catch/throw blocks. learned a lot about promises here :) – sujeet Apr 21 '20 at 07:33
0

You should push Promise object to the array. so just wrap the async function with Promise.

Jack Ting
  • 552
  • 4
  • 6