-1

I have two Promise.all() on the same page like this:

// promiseGroup1
Promise.all([promise1, promise2, promise3]).then(function(values1) {
  doStuff1(values1)
})

// promiseGroup2
Promise.all([promise4, promise5, promise6]).then(function(values2) {
  doStuff2(values2)
})

I want everything to start ASAP, and to let promiseGroup1 to continue to doStuff1() if promiseGroup1 finishes first, but doStuff2() to wait doStuff1()to finish. How can I implement this?

James Z
  • 12,209
  • 10
  • 24
  • 44
lznt
  • 2,330
  • 2
  • 22
  • 27
  • What should block what, if what finishes first??? Currently, it's a bit unclear, exactly what are you asking. – FZs Sep 26 '19 at 18:24

2 Answers2

2

If you want one to wait for the other, then you don't want them to be separate async Promises. Instead of trying to force them to be synchronous, why not just include all the promises in one Promise.all and then call doStuff1() and doStuff2() in sequence in the same .then callback?

Keep in mind that the individual Promises you're passing to Promise.all will begin running as soon as they're created; they won't wait to run for Promise.all. So it's not like putting them in different groups changes which resolve first.

To clarify, what you're asking for is equivalent to this:

Promise.all([promise1, promise2, promise3, promise4, promise5, promise6]).then(function(values1) {
  doStuff1(values1);
  doStuff2(values1.slice(3));
});

Unless doStuff1 has side effects that you want to happen before the second set of promises resolve, if the first set resolves first? That would be weird and probably deserving of refactoring, but for that, just return the second promise from the handler of the first and chain them:

Promise.all([promise1, promise2, promise3]).then(function(values) {
  doStuff1(values1);
  return Promise.all([promise4, promise5, promise6]);
})
.then(function(values) {
  doStuff2(values);
});
IceMetalPunk
  • 5,476
  • 3
  • 19
  • 26
  • 2
    This waits for all 6 promises to settle before it invokes `doStuff1`. He wants to `doStuff1` as soon as promises 1-3 are done. – Arash Motamedi Sep 26 '19 at 18:42
  • 1
    Check my edit for that. But still, that kind of behavior is probably a code smell. The point of using promises is to have asynchronicity; requiring side effects from one asynchronous process before handling another asynchronous process, but trying to decouple them in the code, is just confusing. – IceMetalPunk Sep 26 '19 at 18:45
  • Thanks! The second one is exactly what I want. But how should this be refactored? In my case doStuff1() will build a table based on promise1,2,3 result, doStuff2() will *potentially* change some values in the table based on promise4,5,6 result. – lznt Sep 26 '19 at 19:52
  • Is there anything that uses that table before doStuff2() does (or decides not to)? If not, use my first code that combines them all into one Promise.all(). – IceMetalPunk Sep 26 '19 at 19:56
  • What if I do want to render the table to the user as soon as promise1,2,3 is done, instead of waiting for all promises? – lznt Sep 26 '19 at 20:02
  • 1
    Then, yeah, my second bit of code would be the way to do it. Generally speaking, though, if you have two asynchronous things running that modify the same data, you'd want to wait until both are done before rendering the result. Otherwise, you're going to get "flickering" as the table renders once, then changes when the second bit modifies its contents. That's generally considered poor UI design. A placeholder table rendered with "Loading, please wait..." until all promises resolve is usually even enough. – IceMetalPunk Sep 26 '19 at 20:10
1

Sure!

// promiseGroup1
const stuff1Promise = new Promise(resolveStuff1 => {
   Promise.all([promise1, promise2, promise3]).then(function(values) {
     const stuff1 = doStuff1();
     resolveStuff1(stuff1);
   })
});

// promiseGroup2
Promise.all([promise4, promise5, promise6, stuff1Promise]).then(function(values) {
  doStuff2()
})

This lets both your promise groups to kick off simultaneously, and also ensures that your doStuff2 is invoked only after your doStuff1 has finished.

Arash Motamedi
  • 9,284
  • 5
  • 34
  • 43
  • 1
    Why are you constructing a Promise like that? Promise.all is already a promise. If you're going to do it that way, you can just set `stuff1Promise` equal to the first Promise.all. Although note that both of these methods are the same as just combining the two lists of Promises into a single `Promise.all` anyway. – IceMetalPunk Sep 26 '19 at 18:25
  • 1
    There's a slight distinction. He wants to `doStuff2` after `doStuff1` has been done, not after PG1 has been resolved. So, created a new promise which only resolves after PG1 has resolved AND after `doStuff1` has been done. – Arash Motamedi Sep 26 '19 at 18:28
  • 3
    And this code has the [Explicit Promise Construction Antipattern](https://stackoverflow.com/q/23803743/8376184) – FZs Sep 26 '19 at 18:30
  • Yeah, after having re-read the question, your solution works great. Just that `new Promise` looks redundant. – Sal Rahman Sep 26 '19 at 18:34
  • 1
    @FZs not quite. He doesn't want Promise Group 2 to run after Promise Group 1 has settled; He wants `doStuff2` to run after `doStuff1` has run. And I think there's a goal here to kick off both Promise Groups simultaneously. – Arash Motamedi Sep 26 '19 at 18:34
  • 1
    @ArashMotamedi Using Promise.all doesn't start any promises. The individual promises begin running as soon as they're created. In fact, they may settle even before the Promise.all() line is reached at all! doStuff1 gets called only after PG1 resolves, so there's no difference in this case. – IceMetalPunk Sep 26 '19 at 18:38
  • @IceMetalPunk You are right. I was reading the question as if the promises were being created inline; like `[promise1(), promise2(), promise3()]`... – Arash Motamedi Sep 26 '19 at 18:46
  • `.then(promiseGroup2)` makes no sense. `.then()` accepts a function parameter, not a promise object. – Patrick Roberts Sep 26 '19 at 19:02
  • Thanks! The code seems to be working, but I am not really sure I understand the resolveStuff1 part? – lznt Sep 26 '19 at 20:30
  • 1
    @Iznt That's how you create a new promise. The signature for creating a new promise is a bit confusing at first. It looks like this: `const myPromise = new Promise(function(resolve, reject) {})`, where `resolve` and `reject` are also functions. In the function in your Promise constructor, you do some stuff, and then either call the `resolve` function or the `reject`, which causes your promise to "settle". Some more about Promises and how to create them here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise – Arash Motamedi Sep 26 '19 at 21:05
  • @Iznt What I've done is to create a promise (named `stuff1Promise`) that resolves only after `doStuff1()` has been run. Then in the second Promise Group, I'm waiting for all promises (including Promises 4, 5, 6, and the `stuff1Promise`) to settle before I call `doStuff2()`. – Arash Motamedi Sep 26 '19 at 21:06
  • 1
    You asked a very good question which spurred great conversation. There are various ways to accomplish what you've described. This is one way. Promise Chaining as suggested by IceMetalPunk also works. In my approach, you can replace those promise variables with async functions, and the code still works as expected. – Arash Motamedi Sep 26 '19 at 21:08