0

How does work this promise chain?

Promise.resolve(1)
  .then(x => console.log(1))
  .catch(x => console.log(2))
  .then(x => console.log(3))

Promise.reject(1)
  .then(x => console.log(4))
  //.then(x => console.log(5))
  .catch(x => console.log(6))
  .then(x => console.log(7))

Result 1 6 3 7

Why the result isn't equal 1 3 6 7 ?

If add .then(x => console.log(5)), then result is equal 1 3 6 7. How does it works?

Alexander van Oostenrijk
  • 4,644
  • 3
  • 23
  • 37
snowy
  • 11
  • 8
    You should ***not*** expect any specific order of promise resolutions if the promise chains are not linked. The only guarantee you have is that links of the same promise chain resolve in order related to each other. That's it. – VLAZ Aug 14 '21 at 14:58
  • 1
    @T.J.Crowder did you mean [this search](https://stackoverflow.com/search?q=%5Bjs%5D+promise+chain+order+user%3A157247+is%3Aanswer)? Yours has `user:me`. I was confused why you thought my answers were relevant :P – VLAZ Aug 14 '21 at 15:03
  • @VLAZ - Yes, thank you. I've answered a question that looks **very** much like this one so I was trying to find it. Thanks for that! – T.J. Crowder Aug 14 '21 at 15:06
  • Please note that VLAZ said above about order. Having said that, the fundamental reason is that promise handlers are called asynchronously, and `then` and `catch` create new promises. You start the first chain off first with a fulfilled promise, so its first `then` handler is called on the next async tick ("1"). You start the second chain off with a rejected handler, so its `catch` handler is called on the next async tick ("6"). Then since those handlers fulfill the promises they return, their handlers are called (the first chain's handler logs "3", the second logs "7"). – T.J. Crowder Aug 14 '21 at 15:08
  • 2
    But again, although the order between those two chains is defined provided the code is exactly as shown, it's **not** something you should rely on. If you need coordination between the two promise chains, coordinate them intentionally. :-) – T.J. Crowder Aug 14 '21 at 15:08
  • 2
    @T.J.Crowder just to add to this - this is the explanation *here* as they are resolved in round-robin order. But is not necessarily applicable for any two promise chains. In some cases, it takes more than one tick to resolve an individual promise, so instead of `a b a b a b` the order might be `a b b b a a`. Hence the recommendation to not rely on anything specific. – VLAZ Aug 14 '21 at 15:11
  • However, with immediately resolving promises, as here, the ECMAScript specification would enforce a specific order to be respected. – trincot Aug 14 '21 at 15:13
  • @trincot that is *true* but I fear OP will take "this is why these resolve that way" incorrectly as "expect every single instance of two promise chains to behave like this". Which will be misleading. – VLAZ Aug 14 '21 at 15:27
  • 2
    @VLAZ Make that "*in some cases*" a "*in most cases*". You shouldn't use promises at all if you're always immediately resolving them :-) – Bergi Aug 14 '21 at 16:02
  • @Bergi - Unless you're resolving them to thenables. ;-D Or you have to return a promise on all branches but some branches have no asynchronous processing. Etc. – T.J. Crowder Aug 14 '21 at 17:36
  • 1
    @VLAZ - Very good to underscore that. It's also worth noting that some details in this area (though not *quite* in the code above) changed between versions of the ECMAScript spec. So even more reason for he OP -- and all of us -- to follow your original advice. :-) – T.J. Crowder Aug 14 '21 at 17:38
  • @T.J.Crowder Did you find a suitable duplicate yet, btw? – Bergi Aug 14 '21 at 17:55
  • @Bergi - I gave up. I know it's there (I **answered** it, I remember), but... – T.J. Crowder Aug 14 '21 at 18:02
  • 1
    @T.J.Crowder Same feeling here. I search for keywords like "[promise] unrelated/indepdenent/separate chain order", but all that comes up are [related questions](https://stackoverflow.com/a/62405924/1048572) not this specific case – Bergi Aug 14 '21 at 18:13

1 Answers1

1

As others have mentioned in the comments under the question, you shouldn't rely on the timing or order in which unrelated promises are resolved.

Having said that, there is an explanation of the output your code produces.

Callbacks passed to then() and catch() methods are invoked asynchronously and are enqueued in a micro-task queue.

  1. Calling Promise.resolve(1) creates a resolved promise; as a result, the callback passed to Promise.resolve(1).then(...) is put in a micro-task queue.

    [
      (x) => console.log(1)
    ]
    
  2. After this, Promise.reject(1) is called which creates a rejected promise. This will reject the promise returned by Promise.reject(1).then(...).

    [
      reject(Promise.reject(1).then(...)),
      (x) => console.log(1)
    ]
    
  3. End of execution of your script

  4. Event loop will start processing the micro-task queue which has two tasks in it

    [
      reject(Promise.reject(1).then(...)),
      (x) => console.log(1)
    ]
    
  5. 1 is logged on the console. After logging 1, promise returned by Promise.resolve(1).then(...) is fulfilled with the value of undefined.

    This will lead to the fulfilment of the promise returned by Promise.resolve(1).then(...).catch(...)

    [
      resolve(Promise.resolve(1).then(...).catch(...))
      reject(Promise.reject(1).then(...))
    ]
    
  6. Next task in the queue is to reject the promise returned by Promise.reject(1).then(...). This rejection of promise queues another micro-task

    queue: [
             x => console.log(6),
             resolve(Promise.resolve(1).then(...).catch(...))
           ]
    
    console output: 1
    
  7. Promise returned by Promise.resolve(1).then(...).catch(...) is resolved which queues x => console.log(3) in the micro-task queue

    queue: [
             x => console.log(3),
             x => console.log(6)
           ]
    
    console output: 1
    
  8. "6" is logged on the console

    queue: [
             x => console.log(3),
           ]
    
    console output: 1 6
    
  9. After logging 6, promise returned by the catch method if fulfilled with the value of undefined which queues another micro-task

    queue: [
             x => console.log(7),
             x => console.log(3)
           ]
    
    console output: 1 6
    
  10. 3 is logged on the console

    queue: [
             x => console.log(7),
           ]
    
    console output: 1 6 3
    
  11. 7 is logged on the console

    queue: []
    
    console output: 1 6 3 7
    

If add .then(x => console.log(5)), then result is equal 1 3 6 7.

With an extra then() method call, it takes an extra step in the micro-task processing cycle to queue

x => console.log(6)

in the micro-task queue which results in

x => console.log(3)

getting processed before x => console.log(6).

Yousaf
  • 27,861
  • 6
  • 44
  • 69