3

Given the following code

Promise.resolve().then(() => {
    console.log(0);
    return Promise.resolve(4);
}).then((res) => {
    console.log(res)
})
Promise.resolve().then(() => {
    console.log(1);
}).then(() => {
    console.log(2);
}).then(() => {
    console.log(3);
}).then(() => {
    console.log(5);
}).then(() => {
    console.log(6);
})

i thought that this would give 0 4 1 2 3 5 6 but the output is 0 1 2 3 4 5 6.

Can anyone comment as to why this is the case?

Guerric P
  • 30,447
  • 6
  • 48
  • 86
Strahinja Ajvaz
  • 2,521
  • 5
  • 23
  • 38
  • Is this behavior consistent across browsers? – Guerric P Dec 15 '21 at 23:56
  • 2
    You have two separate promise chains running out of band with each other. If the two promise chains were combined into one, you could guarantee the order of the final result. – 88jayto Dec 15 '21 at 23:59
  • 1
    @88jayto the output is `0 1 2 3 4 5 6`. – Strahinja Ajvaz Dec 16 '21 at 00:01
  • 1
    @StrahinjaAjvaz updated my comment, this also will probably help explain what you're seeing: https://stackoverflow.com/questions/36870467/what-is-the-order-of-execution-in-javascript-promises – 88jayto Dec 16 '21 at 00:14
  • FYI, understanding why this is will not be useful in programming with promises for a number of reasons: 1) If you actually cared about the order between independent promise chains, then you need to not make the chains independent so you can then control the order. 2) Promise specs change from time to time and this level of detail is subject to change, 3) Real code would have real asynchronous operations that have unpredictable completions so this kind of issue would not occur in real asynchronous code. – jfriend00 Dec 16 '21 at 00:31
  • 4) The detailed explanation of something like this is only worked out by studying the exact sequence of events in the promise spec and the exact sequence of events in the event loop for every promise involved in this code (and there are at least 9 promises involved in this code). – jfriend00 Dec 16 '21 at 00:32
  • So, perhaps there's intellectual curiosity about this problem, but understanding every detail of this is not going to be useful for actual real-world programming with promises. It might be time better spent to study things that will be useful in real-world programming. Oh, and I'm pretty sure this question has been asked before here (though I haven't yet found it). – jfriend00 Dec 16 '21 at 00:33
  • @jfriend00 it was intellectual curiosity as i was asked this question and didn't know why it worked the way it did. Thanks a lot for your feedback and detailed review in the other answer! – Strahinja Ajvaz Dec 16 '21 at 00:36
  • If this was an interview question, I'd argue it's not a very useful interview question and I probably would have responded with why this level of detail is not relevant in real-world programming (perhaps that's the right answer to the interview question). It seems that the only real question here is why is `4` positioned after `1`, `2` and `3`. It's easy to see why it's after `1` and `2` because there are clearly more promises that have to get resolved before you see the `4` output than before you see the `1` or even `2` output. – jfriend00 Dec 16 '21 at 00:41
  • So, what's left is explaining why `4` is after `3`. That will likely involve diving into minutiae details of the promise specs and probably has something to do with what happens with the timing when you return a promise from a `.then()` handler as opposed to just returning the value directly. – jfriend00 Dec 16 '21 at 00:42

1 Answers1

4

First off, this is two independent promise chains. In real world asynchronous coding the timing between different steps in these two independent chains is entirely disconnected. So, this is not a real-world problem at all. It's a purely academic exercise or one of intellectual curiosity.

In real world coding, if you cared about the relative order between the operations in these two chains, you would not have two independent promise chains. Instead, you'd link them into one chain where you can control the order.

Oh, and there can even occasionally be changes to the Javascript/promise spec that might influence this level of detail too. A few years ago, a change in the spec was made to how await works in order to improve promise performance and that did affect detailed timings in these type of test scenarios (when using await).


That said, the only real question is why the output for 4 comes where it does. We can initially examine the problem by changing the code to have the first .then() handler not return Promise.resolve(4), but to instead just do return 4;. When we do that, this is what you get:

Promise.resolve().then(() => {
    console.log(0);
    return 4;
}).then((res) => {
    console.log(res)
})
Promise.resolve().then(() => {
    console.log(1);
}).then(() => {
    console.log(2);
}).then(() => {
    console.log(3);
}).then(() => {
    console.log(5);
}).then(() => {
    console.log(6);
})

And, you get the output:

0
1
4
2
3
5
6

OK, so now the problem is reduced to why does return Promise.resolve(4) cause 4 to be delayed two spots in the order vs. return 4;?

This has to do with the difference in returning a promise from a .then() handler vs. returning a plain value. When you return a value, the next promise in the chain can be immediately resolved with that value and the succeeding .then() handlers attached to that promise can be immediately added to the promise job queue.

But, when you return a promise, the next promise in the chain has to just monitor that returned promise by adding a .then() handler to it and waiting for it to get resolved. Even though it is already technically resolved,, it's still the case that the only way you get the value out of the promise is by attached a .then() handler to it (or using await) so that still has to happen. This allows other things to get into the promise job queue ahead of it.

So, you can immediately see that the 4 output will be delayed some. This delay allows 2 to be output next. Then, the .then() handler attached to Promise.resolve(4) by the parent promise gets to execute, so now the first promise chain gets to begin to move onto the next step. To do that, it adds it's next .then() handler job in the promise job queue. But, it's not the next job in the queue. The next job in the queue was put there from the 2nd chain after it output 2. So, we get that job next and the output 3 comes and the finally 4 gets to go after that.

So by doing return Promise.resolve(4), we not only delayed this chain by one cycle, but we also allowed the next event in the second promise chain to get scheduled ahead of the next event in the first chain - thus essentially costing us two positions in the job queue.

So, the difference between return Promise.resolve(4) and return 4 is that the subsequent .then() handler drops two spots in the relative order here due to the timing of adding .then() handlers to the promise job queue.

jfriend00
  • 683,504
  • 96
  • 985
  • 979