7

Trying to learn some modern JS and in particular the ECMAScript 6 Promises. I'm playing with this simple test:

let slow = new Promise((resolve) => {
  setTimeout(function()
  {  
 console.log('slow');
 resolve(); 
  }, 2000, 'slow');
});

let instant = new Promise((resolve) => {
 console.log('instant');
 resolve(); 
});

let quick = new Promise((resolve) => {
  setTimeout(function()
  {  
 console.log('quick');
 resolve(); 
  }, 1000, 'quick');
});

Promise.all([slow, instant, quick]).then(function(results) {
  console.log('finished');
}, function(error) {
  console.log(error); 
});

What I want here is to start all Promises async at the same time. And log when they are all finished. In the console this shows as expected: "instant", "quick", "slow" and "finished".

However what if I wanted to be sure that "instant" doesn't echo/log before "slow" has completed? That is, I want the console to log "quick", "slow", "instant" and "finished"...but at the same time, they must still all start at the same time async.

How can I achieve this?

Ajedi32
  • 45,670
  • 22
  • 127
  • 172
Werner
  • 1,229
  • 1
  • 10
  • 24
  • 2
    promise chaining, not promise.all – Ronnie Royston Oct 25 '16 at 19:21
  • 1
    Chain the promises with .then() – rtribaldos Oct 25 '16 at 19:23
  • 1
    I've updated my question, because wouldn't that mean that the Promises run in sync? I need them to start at the same time, async, which is what Promise.all gives me I believe. For example, imagine that the 3 Promises were fetching some data, but I have to be sure to show the data in a specific order. – Werner Oct 25 '16 at 19:31
  • 1
    Your promises start executing the moment you call `new Promise` actually. I will have some details of getting what you want in my answer. – tcooc Oct 25 '16 at 19:32
  • @Werner Is Question _"How to wait for another promise?"_ or _"they must still all start at the same time async."_? – guest271314 Oct 25 '16 at 20:22

6 Answers6

4

Part of the issue is the logging is happening in the setTimeout method, and not actually from the promise resolution.

const slow = new Promise((resolve) => {
  setTimeout(() => {
    console.log('slow - from setTimeout');
    resolve('slow - from resolve');
  }, 2000, 'slow');
});

const instant = new Promise((resolve) => {
  console.log('instant - from setTimeout');
  resolve('instant - from resolve');
});

const quick = new Promise((resolve) => {
  setTimeout(() => {
    console.log('quick - from setTimeout');
    resolve('quick -from resolve');
  }, 1000, 'quick');
});

Promise.all([slow, instant, quick]).then((results) => {
  console.log(results);
  console.log('finished');
}, (error) => {
  console.log(error);
});

Passing in the value to the resolve method will return everything in the Promise.all. The response comes back from each promise as an array, and you can iterate through those responses once all are complete.

Ajedi32
  • 45,670
  • 22
  • 127
  • 172
Kelly J Andrews
  • 5,083
  • 1
  • 19
  • 32
  • 1
    Solid answer. This nicely illustrates the difference between logging the result in the promise and returning the result for use in `Promise.all`. – Ajedi32 Oct 25 '16 at 20:34
  • Thanks @Ajedi32 - need to remember to use the snippet feature. super simple. – Kelly J Andrews Oct 25 '16 at 20:43
  • Yeah I didn't realize it now displayed the results of `console.log`. That's a really useful feature for pure JavaScript questions like these. – Ajedi32 Oct 25 '16 at 20:46
4

So to be clear, what you want to do here is kick off all the promises at once and display the results of each promise in a particular order as they come in, correct?

In that case, I'd probably do it like this:

let slow = new Promise((resolve) => {
  setTimeout(function()
  {
    // Rather than log here, we resolve to the value we want to log
    resolve('slow');
  }, 2000, 'slow');
});

let instant = new Promise((resolve) => {
    resolve('instant');  
});

let quick = new Promise((resolve) => {
  setTimeout(function()
  {  
    resolve('quick');  
  }, 1000, 'quick');
});

// All Promises are now running. Let's print the results...

// First wait for the result of `slow`...
slow.then((result) => {
  // Result received...
  console.log(result);
  
  // Now wait for the result of instant...
  instant.then((result) => {
    
    // Result received...
    console.log(result);
    
    // Now wait for the result of quick...
    quick.then((result) => {
      
      // Result received...
      console.log(result);
      
    }).then((result) => {
      // Done
      console.log('finished');
    });
  });
});

Notice that unlike cchamberlain's answer, this method does not wait for all promises to resolve before it starts returning results. It returns the results as they come in, but without violating your requirement of keeping the results in-order. (To verify this, try changing the wait time of quick to 2500ms, and observe that its result is printed 500ms after instant.) Depending on your application, this may be desirable.

The above code is a bit messy, but thankfully with the new async/await syntax in ES2017 it can be made much cleaner:

let slow = new Promise((resolve) => {
  setTimeout(function()
  {
    // Rather than log here, we resolve to the value we want to log
    resolve('slow');
  }, 2000, 'slow');
});

let instant = new Promise((resolve) => {
    resolve('instant');  
});

let quick = new Promise((resolve) => {
  setTimeout(function()
  {  
    resolve('quick');  
  }, 1000, 'quick');
});

// All Promises are now running. Let's print the results...

async function logResults(...promises) {
  for (let promise of promises) {
    console.log(await promise);
  }
}

logResults(slow, instant, quick).then(() => console.log('finished'));

Try in Babel. Note: The above code doesn't currently work in modern browsers without Babel (as of October 2016). In future browsers it will.

Community
  • 1
  • 1
Ajedi32
  • 45,670
  • 22
  • 127
  • 172
  • How is first example different from [this](http://stackoverflow.com/a/40248279/2801559) Answer? `new Promise()` is called when `instant` is assigned to the result of `new Promise()` constructor. – guest271314 Oct 25 '16 at 20:24
  • @guest271314 This answer kicks off all async operations at once instead of waiting for `slow` to complete before kicking off `quick`, as that answer does. – Ajedi32 Oct 25 '16 at 20:28
  • @Ajedi32 this is exactly what I was looking for. Both the first example and also the next async/await that shows how the future would would avoid building up pyramids. Thanks to all answers everyone, really helpful in my journey! – Werner Oct 26 '16 at 06:45
  • Forgive my (obvious) ignorance, but what does "..." mean in the function arguments? E.g. "logResults(...promises)"? And "logResults(promises)" doesn't work which I'd expect... – Werner Oct 26 '16 at 09:54
  • Answering my own question here: I'ts called the spread syntax. Thanks. – Werner Oct 26 '16 at 10:24
  • @Werner It's actually only called the spread operator when used in function _calls_ (and array literals, etc). When used in function _definitions_, it's known as "rest parameters". See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator and https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters – Ajedi32 Oct 26 '16 at 14:40
2

UPDATED

You cannot kick them all off at the same time and expect the results to log in the order you are asking for with your current code. You could do something like the following:

let slow = new Promise((resolve) => {
  setTimeout(() => resolve('slow'), 2000)
})

let instant = new Promise((resolve) => {
  // In the future setImmediate can be used here to ensure async execution. For now, setTimeout with 0MS effectively does the same thing.
  setTimeout(() => resolve('instant'), 0)
})

let quick = new Promise((resolve) => {
  setTimeout(() => resolve('quick'), 1000)
})

Promise.all([slow, instant, quick]).then(function(results) {
  for(let result of results) {
    console.log(result)
  }
  console.log('finished')
}, function(err) {
  console.error(err)
})

This schedules them all then prints in the correct order upon completion.

cchamberlain
  • 17,444
  • 7
  • 59
  • 72
  • 2
    That's not true. The order you get from this is "instant, quick, slow" whereas OP wants "quick, slow, instant". – Madara's Ghost Oct 25 '16 at 19:41
  • 3
    Note, `Promise.all()` returns an array of results in the order which the `Promise` or value is set at index of array passed to `Promise.all()`, not the order in which the `Promise` is resolved. See [`Promise.all()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all#Description) _"The array of values maintains the order of the original iterable object, not the order that the promises were resolved in."_ , [promise.all inside a forEach loop — everything firing at once](http://stackoverflow.com/q/37089515/) – guest271314 Oct 25 '16 at 20:05
  • @cchamberlain Note, `instant` is resolved before `slow` at `javascript` at Answer. The array returned at `.then()` chained to `Promise.all()` will be in the same order as the elements set at array passed to `Promise.all()`. – guest271314 Oct 25 '16 at 20:11
  • 1
    @guest271314 - I'm sure that `slow` is resolved *after* `instant`, that is not at question. `Promise.all` ensures the printing order will be correct when they have all resolved. I don't think OP cares as much about when they get resolved, but more so how to orchestrate the results. Correct me if I'm wrong @Werner. – cchamberlain Oct 25 '16 at 20:17
  • @cchamberlain _"I don't think OP cares as much about when they get resolved, but more so how to orchestrate the results. Correct me if I'm wrong"_ Well, the actual Question is _"How to wait for another promise?"_ , though OP includes _"they must still all start at the same time async."_ within body of Question. Not clear from Question itself. – guest271314 Oct 25 '16 at 20:20
  • @guest271314 - I agree, the title should be updated to fit the question a little better. – cchamberlain Oct 25 '16 at 20:26
  • 2
    @cchamberlain - yes the title is much better now. Learning a lot both from the answers and comments, excellent! While I was thinking specifically about the resolve, I did not know about the ordering of the Promise.all() which you used to actually solve the problem. – Werner Oct 25 '16 at 21:40
0

If the requirements of Question are

"instant" doesn't echo/log before "slow"

and

but at the same time, they must still all start at the same time async.

you need only reorder the elements within iterable object passed to Promise.all(), or adjust the resulting array within .then() chained to Promise.all() before calling console.log() on each element of resulting array.

If requirement is

"How to wait for another promise?"

or

"How do I guarantee resolution order of multiple promises?"

see this Answer.


Promise.all passes an array of values from all the promises in the iterable object that it was passed. The array of values maintains the order of the original iterable object, not the order that the promises were resolved in. If something passed in the iterable array is not a promise, it's converted to one by Promise.resolve.

let slow = new Promise((resolve) => {
  setTimeout(function(value) {  
 resolve(value); 
  }, 2000, "slow");
});

let instant = new Promise((resolve) => {
 resolve("instant"); 
});

let quick = new Promise((resolve) => {
  setTimeout(function(value) {  
 resolve(value); 
  }, 1000, "quick");
});

Promise.all([slow, instant, quick]).then(function(results) {
  console.log("finished");
  console.log(results.join("\n"))
}, function(error) {
  console.log(error); 
});
Community
  • 1
  • 1
guest271314
  • 1
  • 15
  • 104
  • 177
-1

Promise.all() is not necessary to return expected result.

You can utilize function calls to return the Promise constructor, pass the value which should be passed to console.log() to resolve() or reject(). Push the value to an array. Use .then() to process the returned Promise value, push the returned value to the array, return array to callback function parameter at next .then() in chain. Access the chained array of Promise values at last .then() in chain.

let results = [];

let pace = value => {console.log(value); results.push(value); return results};

let slow = () => new Promise((resolve) => {
  setTimeout((value) => {  
    resolve(value);  
  }, 2000, "slow");
});

let instant = () => new Promise((resolve) => {
    resolve("instant");  
});

let quick = () => new Promise((resolve) => {
  setTimeout((value) => {  
    resolve(value);  
  }, 1000, "quick");
});

slow().then(pace)
.then(instant).then(pace)
.then(quick).then(pace)
.then(res => console.log("finished, results:", res))
.catch(error => console.log(error));
guest271314
  • 1
  • 15
  • 104
  • 177
  • Code works now, but still doesn't fulfill the OP's requirement of not waiting until the first promise is resolved before kicking off the next one. – Ajedi32 Oct 25 '16 at 19:57
  • @Ajedi32 _"but still doesn't fulfill the OP's requirement of not waiting until the first promise is resolved before kicking off the next one"_ What do you mean? Where is next function called before current function is called? – guest271314 Oct 25 '16 at 20:09
  • The OP states "What I want here is to start all Promises async at the same time." This method doesn't fulfill that requirement, as it doesn't start running `quick` until after `slow` has already completed. The result is that you spend 3000ms total waiting on async operations instead of 2000. – Ajedi32 Oct 25 '16 at 20:27
  • @Ajedi32 The original Question is ambiguous; not clear. It is not possible to both _"How to wait for another promise?"_ and _"What I want here is to start all Promises async at the same time."_ simultaneously. – guest271314 Oct 25 '16 at 20:33
  • I believe the term you're looking for is "contradictory", not "ambiguous". And no, I don't think the OP's requirements are inherently contradictory. It seems pretty clear to me he wants to start all async operations at the same time, but print all their results out in a specific order which may not necessarily be the order the promises resolve in. – Ajedi32 Oct 25 '16 at 20:37
-1

First of all, your code already starts all 3 promises at the "same time". You are also logging "finished" correctly. From what I understand from the question, you want to handle the results of the promises in sequential order, but have them execute in parallel.

let slow = new Promise((resolve) => {
  setTimeout(function()
  {  
    resolve();
  }, 2000);
});

let instant = new Promise((resolve) => {
    resolve();
});

let quick = new Promise((resolve) => {
  setTimeout(function()
  {  
    resolve();
  }, 1000);
});

instant.then(function(results) {
  console.log("instant");
}).then(function(){return quick;}).then(function(results) {
  console.log("quick");
}).then(function(){return slow;}).then(function(results) {
  console.log("slow");
}).then(function(){ return Promise.all([slow, instant, quick]);}).then(function(results) {
  console.log('finished');
}).catch(function(error) {
  console.log(error);   
});

This will guarantee you'll handle the resolutions in order.

Note: In your example, you use setTimeout, which is guaranteed to call the handlers in the order of the time, so your existing code will already log "instant", "quick", "slow", "finished". The code I provided guarantees this order for any set of promises with different resolve times.

tcooc
  • 20,629
  • 3
  • 39
  • 57
  • This seems to resolve everything instantly, without waiting for any of the promises to complete. – Ajedi32 Oct 25 '16 at 19:46
  • @Ajedi32 updated to handle promise chaining properly. Not sure which library I was thinking of but ES Promises need functions. – tcooc Oct 25 '16 at 21:14
  • Cool. You may want to change the order though to match what the OP had in the question (slow, instant, quick) instead of what you have now (instant, quick, slow). – Ajedi32 Oct 25 '16 at 21:29