Mojimi
Mojimi

Reputation: 3161

How to avoid using two loops when using Promise.all() dynamically?

Whenever you see Promise.all() being used, it is usually used with two loops, how can I make it into only one without using async and mantaining execution order?

The question is not about the order, I know promise.all preserves order, the question is about how to avoid two loops when you just need the returned value

function timeout(x){
 return new Promise( resolve => {
  setTimeout( () => {
    return resolve(x);
  },x)
 })
}

const promises = [];
const results = [];
//First loop, array creation
for (i = 0; i < 20; i++) { 
  const promise = timeout(i*100)
  promises.push(promise);
}

Promise.all(promises).then( resolvedP => {
    //Second loop, async result handling
    resolvedP.forEach( (timeout,i) => {
    results.push({
      index : i,
      timeout : timeout
    })
  })
  console.log(results);
})
//End of code

Now, this can be solved with async but in my context I can't use it, for example :

//Only one loop
for (i = 0; i < 20; i++) {
  const timeoutAwait = await timeout(i*100);
  results.push({
      index : i,
      timeout : timeoutAwait
    })
}
console.log(results)
//End of code

What I have tried is the following, but the promise doesn't return the resolve value without using .then() :

for (i = 0; i < 20; i++) { 
  const promise = timeout(i*100)
  promises.push(promise);
  results.push({index : i, timeout : promise});
}

Promise.all(promises).then( resolvedP => {
    resolvedP.forEach( (timeout,i) => {
      results.push({
        index : i,
        timeout : timeout
      })
    })
    console.log(results);
    //results[0].timeout is a Promise object instead of 0
})
//End of code

So, is there any way I can make my first code sample in only one loop? Please ignore the context, is only an example.

Upvotes: 1

Views: 362

Answers (3)

k0pernikus
k0pernikus

Reputation: 66500

As by the Promise.all documentation, the order will be preserved. It says about the return value:

A pending Promise in all other cases. This returned promise is then resolved/rejected asynchronously (as soon as the stack is empty) when all the promises in the given iterable have resolved, or if any of the promises reject. See the example about "Asynchronicity or synchronicity of Promise.all" below. Returned values will be in order of the Promises passed, regardless of completion order.

(Highlighted by me.)

Upvotes: 1

HMR
HMR

Reputation: 39270

I find it more easy to understand if you map over an array of values instead of looping:

const timeout = x=>Promise.resolve(x);

Promise.all(
  [...new Array(3).keys()]//array [0,1,2]
    .map(x=>timeout(x*100))//[timeout(0*100),timeout(1*100),...
).then(
  result=>result.map(//results of the timeout, map to object
    (timeout,index)=>({
      index,
      timeout
    })
  )
).then(
  result=>console.log('result:',result)
)

It's usually not a good idea to asynchronously mutate a shared variable (the results array). Just have the promise resolve and create the result you need in the last .then, now your promise resolves to the value you want.

Upvotes: 0

ponury-kostek
ponury-kostek

Reputation: 8060

function timeout(x) {
  return new Promise(resolve => {
    setTimeout(() => {
      return resolve(x);
    }, x);
  });
}

const promises = [];
const results = [];
//First loop, array creation
for (let i = 0; i < 20; i++) {
  const promise = timeout(i * 100).then(x => results.push({
    index: i,
    timeout: x
  }));
  promises.push(promise);
}
Promise.all(promises).then(() => {
  console.log(results);
});

If you want to preserve execution/results order assign results using i index instead of .push

const promise = timeout(i * 100).then(x => results[i] = {
  index: i,
  timeout: x
});

Upvotes: 1

Related Questions