1

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.

Mojimi
  • 2,561
  • 9
  • 52
  • 116
  • Why? Whats wrong with it? Maybe `Promise.all(promises).then(res => res.map((timeout, index) => ({ timeout, index })))` ? – Jonas Wilms Dec 03 '18 at 15:00
  • 1
    not sure how you would do it without two loops since you have a loop to create the multiple calls and a loop to handle all the responses when they come back... – epascarello Dec 03 '18 at 15:00
  • 1
    Is there a particular reason you need `Promise.all` here? For example, because you want to save the execution/handling of the promises for later? Because otherwise you could just use the `then` method for each promise in the for loop. – Khauri Dec 03 '18 at 15:01
  • @KhauriMcClain I'm not sure, I thought promise.all was needed to maintain execution order – Mojimi Dec 03 '18 at 15:02
  • 1
    Define what you mean by "execution order." If you're trying to avoid race conditions, then don't use `Promise.all` since it more or less runs all the promises simultaneously, not in sequence. If you just need to have an array that's lined up, then you don't *really* need `Promise.all`, just make use of the indices that are passed in. But based on your code I'm seeing sort of an XY problem starting to show. – Khauri Dec 03 '18 at 15:08
  • @KhauriMcClain I just need the array to be lined up, not actually executed in sequence – Mojimi Dec 03 '18 at 15:12
  • 1
    Ok then I believe ponury-kostek 's answer should work for you. – Khauri Dec 03 '18 at 15:15
  • 1
    The order should already be preserved: https://stackoverflow.com/a/28066851/457268 – k0pernikus Dec 03 '18 at 15:17
  • 1
    Possible duplicate of [Promise.all: Order of resolved values](https://stackoverflow.com/questions/28066429/promise-all-order-of-resolved-values) – k0pernikus Dec 03 '18 at 15:17
  • Why don't you want to use `async` keyword? – k0pernikus Dec 03 '18 at 15:22
  • @k0pernikus I'm forced to use a private framework built on top of dojo 1.7 and it just does not play nicely with async – Mojimi Dec 03 '18 at 15:24

3 Answers3

1

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
});
ponury-kostek
  • 7,824
  • 4
  • 23
  • 31
  • Will this guarantee execution order? In this case using timeout it will obviously be in order, but you're already calling then so I don't see why promise.all is necessary. – Mojimi Dec 03 '18 at 15:06
  • 1
    @Mojimi `Promise.all` is necessary to wait for all promises to finish – ponury-kostek Dec 03 '18 at 15:10
1

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.)

k0pernikus
  • 60,309
  • 67
  • 216
  • 347
0

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.

HMR
  • 37,593
  • 24
  • 91
  • 160
  • Well, map being called twice still counts as two loops, I think ponury-kostek's answer nailed what I was missing – Mojimi Dec 03 '18 at 15:19
  • @Mojimi I added a little more explanation. You have to loop twice over it. First to get map to timeout and then map the result values of timeout to objects. This answer was added to point out that you should avoid the loops and asynchronous mutation of a shared variable in the loops. – HMR Dec 03 '18 at 15:21