Execution order of Javascript promises when different promise chains attached

Viewed 189

I wanted to check the portability of a promise object by creating an promise and passing it around and let others attach thens to it. The output is far from what I expected and I don't understand how it executed.

const wait = time => {
  return new Promise((res, rej) => {
    setTimeout(res, time);
  });
};

const promise = wait(5000)
  .then(() => console.log('1'));

promise
  .then(() => console.log('2'))
  .then(() => console.log('3'));

promise
  .then(() => console.log('4'))
  .then(() => console.log('5'));

output:

1
2
4
3
5

I debug the code and here is what I observed. calling wait(5000) invokes constructor of Promise that calls setTimeout API by passing res callback and Promise object is returned back. then it starts registering all the thens from 1 to 5 in the same order. After 5 seconds it calls res from Promise object which invokes the promise chains.

In my mind, then API adds all the callbacks to an array or something like that, from 1 to 5 and once res is called it starts calling callbacks from the array one by one (FIFO) which should produces logs 1 2 3 4 5 not 1 2 4 3 5.

How is this not executed in order they are being registered to the main promise?

1 Answers

First off, when you do this:

promise.then(...).then(...)

That attaches one .then() handler to promise and the second .then() is attached to a new promise that the first .then() returns.

So, when promise resolves, the first .then() is scheduled to fire on the next tick of the event loop. Then, only when the first .then() finishes, it schedules the second .then() to fire on the next tick of the event loop (it does not run on this tick of the event loop).

When you do this:

promise
  .then(() => console.log('2'))
  .then(() => console.log('3'));

promise
  .then(() => console.log('4'))
  .then(() => console.log('5'));

And, then when promise resolves, then(2) and then(4) which are the only two .then() handlers in this piece of code directly attached to promise are scheduled to run on the next tick of the event loop. Upon that next tick, first then(2) runs and then then(4) runs. Since they were both attached to the same promise, they run in a FIFO manner, the first one to be attached runs first. When then(2) runs, it schedules then(3) to run on the next tick of the event loop and when then(4) runs, it schedules then(5) to run on the next tick of the event loop.

So, after both then(2) and then(4) have run, upon the next tick of the event loop, you will see then(3) and then(5) run and that explains the sequencing you see in the console of:

1
2
4
3
5

I presume you're not surprised that then(1) runs first so I didn't add that to the explanation.

How is this not executed in order they are being registered to the main promise?

The three .then() handlers attached directly to promise do run in the order they were attached. What is apparently confusing you is that the chained .then() handlers are not attached directly to promise, but to new promises that promise.then() returns.

.then() returns a new promise because it may not resolve immediately. If the callback inside of .then() itself returns a promise, then the promise that .then() returns does not immediately resolved. It has to wait until the promise that the .then() callback returned also resolves.

In my mind, then API adds all the callbacks to an array or something like that, from 1 to 5 and once res is called it starts calling callbacks from the array one by one (FIFO) which should produces logs 1 2 3 4 5 not 1 2 4 3 5.

Well: 1, 2 and 4 are indeed added to the same array of .then() handler callbacks for the promise object. But, as explained above, 3 and 5 are on separate promises that were created by the .then() they are chained to. So, 1, 2 and 4 are executed FIFO. But 3 and 5 are delayed to a subsequent tick of the event loop.

Related