2

    new Promise((resolve,reject) => {
        console.log('outer promise')
        resolve()
    })
    .then(() => {
        console.log('outer 1 then')
        new Promise((resolve,reject) => {
            console.log('in promise')
            resolve()
        })
        .then(() => {
            console.log('in 1 then')
            return Promise.resolve()
        })
        .then(() => {
            console.log('in 2 then')
        })
    })
    .then(() => {
        console.log('outer 2 then')
    })
    .then(() => {
        console.log('outer 3 then')
    })
    .then(() => {
        console.log('outer 4 then')
    })  

Here are my explanation:

1.Execute the new Promise,output outer promise

2.Execute the first outer then,its callback go to the microtask queue.The second outer then,the third outer then and the fourth outer then execute later,but all of their callback don't go to the queue since each promise returned by each then still in pending state.

3.Execute the callback of the first outer then,and output outer 1 then.After that,new Promise will output in promise

4.Execute the first inner then,its callback go to the microtask queue.The callback of the second inner then don't go to the queue

5.Now,the callback of the first outer then has totally finished,which means the promise returned by the first outer then has resolved.Thus,the callback of the second outer then go to the queue

6.Execute the first task of the queue,it will output outer 2 then and makes the callback of the third then go to the queue.Next,execute the callback of the second inner then,it will output in 2 then

7.Execute the callback of the third outer then,it will output outer 3 then and makes the callback of the fourth then go to the queue.

8.At last,execute the callback of the fourth outer then,it will output outer 4 then

Therefore,the output order should be:

outer promise
outer 1 then
in promise
in 1 then
outer 2 then
in 2 then
outer 3 then
outer 4 then

But it actually ouput:

outer promise
outer 1 then
in promise
in 1 then
outer 2 then
outer 3 then
outer 4 then
in 2 then

What I am confused is why in 2 then will output at last? And why outer 2 then,outer 3 then,outer 4 then will output continuously? This is different from what I have thought about event loop.I thought the result is something to do with the statement return Promise.resolve(),but I don't know what the statement exactly do and why it will influenced the output order.

Chor
  • 833
  • 1
  • 5
  • 14
  • in the first then you have to return the promise – Krzysztof Krzeszewski Jul 23 '20 at 11:00
  • I know,each `then` will return a promise – Chor Jul 23 '20 at 11:29
  • I'm talking about your `new Promise` call inside of the first then, if you don't return it, that then will resolve instantaneously – Krzysztof Krzeszewski Jul 23 '20 at 11:46
  • But here it also resolve instantaneously since the promise is in resolved state – Chor Jul 23 '20 at 12:06
  • No it would not. The `then` call to the promise will return a new pending promise. That will only resolve (or reject) if the callback is executed. `Promise.resolve().then(x => x) //=> Promise { : "pending" }` – 3limin4t0r Jul 23 '20 at 14:27
  • Yes,I know.What I mean is the code `.then(() => { console.log('in 1 then') return Promise.resolve() })`.When execute the callback inside the `then` method,it will first log something and then return a promise.All these 2 statements are executed synchronously.And after that,since the callback has finished,the promise returned by the `then` method will fullfilled,which means the callback inside the next `then` method will go to the queue.According to this train of thought,I write down the output order above.But as you have know,it is wrong.And I don' know in which step I have made a mistake. – Chor Jul 24 '20 at 04:08

2 Answers2

1

Before explaining the output of your code, it is important to note that real-world code should not rely on the timing of promises in unrelated promise chains. If you want one promise to settle after/before another promise, instead of relying on the timing in which both of them will be resolved, create a promise chain that settles the promises in a sequential manner.

Having said that, the key to understanding the output of your code is to understand how the promise returned by the nested then() method

...
.then(() => {
  console.log('in 1 then')
  return Promise.resolve()
})
...

is resolved.

Following steps explain the output:

  1. As the executor function passed to the Promise constructor is called synchronously, first thing that gets logged on the console is 'outer promise'

    micro-task queue: [ ]
    
    console output:
    ---------------
    outer promise
    
  2. After the above console.log statement, resolve function is called. As a result, the newly created promise is resolved synchronously.

    This queues a job in the micro-task queue to execute the fulfilment handler passed to the then() method.

    micro-task queue: [ job('outer 1 then') ]
    
    console output:
    ---------------
    outer promise
    
  3. After synchronous execution of the script ends, javascript can start processing the micro-task queue.

    First job in the micro-task is dequeued and processed. As a result, 'outer 1 then' and 'in promise' are logged on the console.

    micro-task queue: [ ]
    
    console output:
    ---------------
    outer promise
    outer 1 then
    in promise
    

    As the nested promise is resolved as a result of resolve() function call, a job is enqueued in the micro-task queue to execute its fulfilment handler.

    ...
    .then(() => {
      console.log('in 1 then');
      return Promise.resolve();
    })
    ...
    
    micro-task queue: [ job('in 1 then') ]
    
    console output:
    ---------------
    outer promise
    outer 1 then
    in promise
    
  4. As the callback function of the outer 1st then() method ends, implicitly returning undefined leads to the fulfilment of the promise returned by the wrapper then() method. As a result, another job is enqueued in the micro-task queue to execute the outer 2nd then() method's callback function

    micro-task queue: [ job('in 1 then'), job('outer 2 then') ]
    
    console output:
    ---------------
    outer promise
    outer 1 then
    in promise
    
  5. Next job in the micro-task queue is dequeued and processed. 'in 1 then' is logged on the console and as the return value of the callback function is a promise, i.e. Promise.resolve(), the promise returned by the wrapper inner then() method is resolved to the promise returned by its callback function.

    Following code example demonstrates how the promise returned by the then() method is resolved to the promise returned by its callback function:

    const outerPromise = new Promise((resolveOuter, rejectOuter) => {
       const innerPromise = new Promise((resolveInner, rejectInner) => {
          resolveInner(); 
       });
    
       // resolve/reject functions of the outer promise are 
       // passed to the `then()` method of the inner promise 
       // as the fulfilment/rejection handlers respectively
       innerPromise.then(resolveOuter, rejectOuter);
    });
    

    This means that the outer promise (returned by the then() method) now depends on the inner promise (return by its callback function). A job is enqueued in the micro-task queue to resolve the outer promise to the inner promise.

    micro-task queue: [ job('outer 2 then'), job(resolve(outerPr, innerPr) ]
    
    console output:
    ---------------
    outer promise
    outer 1 then
    in promise
    in 1 then
    
  6. Next job in the micro-task queue is dequeued and processed. 'outer 2 then' is logged on the console.

    As the return value of the callback function is undefined, the promise returned by the wrapper outer second then() method is resolved. This enqueues a job in the micro-task queue to execute the fulfilment handler passed to the outer 3rd then() method.

    micro-task queue: [ job(resolve(outerPr, innerPr), job('outer 3 then') ]
    
    console output:
    ---------------
    outer promise
    outer 1 then
    in promise
    in 1 then
    outer 2 then
    
  7. Next job in the micro-task queue is dequeued and processed.

    As the inner promise is resolved, a job is enqueued in the micro-task queue to resolve the outer promise. (resolve function of the outer promise is registered as a fulfilment handler of the inner promise) (see step 5)

    micro-task queue: [ job('outer 3 then'), job(resolve(outerPr) ]
    
    console output:
    ---------------
    outer promise
    outer 1 then
    in promise
    in 1 then
    outer 2 then
    
  8. Next job in the micro-task queue is dequeued and processed. 'outer 3 then' is logged on the console. A job is enqueued in the micro-task queue to execute the fulfilment handler passed to the 4th outer then() method.

    micro-task queue: [ job(resolve(outerPr), job('outer 4 then') ]
    
    console output:
    ---------------
    outer promise
    outer 1 then
    in promise
    in 1 then
    outer 2 then
    outer 3 then
    
  9. Next job in the micro-task queue is dequeued and processed. This resolves the promise returned by the 1st inner then() method. As a result, a job is enqueued in the micro-task queue to execute the fulfilment handler passed to the 2d inner then() method.

    micro-task queue: [ job('outer 4 then'), job('in 2 then') ]
    
    console output:
    ---------------
    outer promise
    outer 1 then
    in promise
    in 1 then
    outer 2 then
    outer 3 then
    
  10. Finally, the last two jobs in the micro-task queue are dequeued and processed one after the other, logging 'outer 4 then' and 'in 2 then' on the console respectively.

    micro-task queue: [ ]
    
    console output:
    ---------------
    outer promise
    outer 1 then
    in promise
    in 1 then
    outer 2 then
    outer 3 then
    outer 4 then
    in 2 then
    
Yousaf
  • 27,861
  • 6
  • 44
  • 69
0

I would shove this in the corner undefined behaviour. You shouldn't rely on the internals of the JavaScript engine to execute then callbacks in a certain order. I can imagine two scenarios that suit the given code.

  1. The inner tasks should be run before the outer tasks. Here you should make sure you return the inner promise. You can then move the inner then calls and add them to the outer promise chain.

    new Promise((resolve,reject) => {
      console.log('outer promise');
      resolve();
    })
    .then(() => {
      console.log('outer 1 then');
      return new Promise((resolve,reject) => { // <- added a return statement
        console.log('in promise');
        resolve();
      });
    })
    .then(() => {
      console.log('in 1 then');
      return Promise.resolve();
    })
    .then(() => {
      console.log('in 2 then');
    })
    .then(() => {
      console.log('outer 2 then');
    })
    .then(() => {
      console.log('outer 3 then');
    })
    .then(() => {
      console.log('outer 4 then');
    });
  2. You want to split off a path for the inner promise and don't care that further then callbacks execute before or after this separate path. The execution order between the two different paths should not be assumed. However within a path you can control the order. In the example below in 2 then should always be executed after in 1 then. The order of in 1 then and outer 2 then should not be assumed.

    const splitPoint = new Promise((resolve,reject) => {
      console.log('outer promise');
      resolve();
    })
    .then(() => {
      console.log('outer 1 then');
      return new Promise((resolve,reject) => { // <- added a return statement
        console.log('in promise');
        resolve();
      });
    });
    
    const path1 = splitPoint.then(() => {
      console.log('in 1 then');
      return Promise.resolve();
    })
    .then(() => {
      console.log('in 2 then');
    });
    
    const path2 = splitPoint.then(() => {
      console.log('outer 2 then');
    })
    .then(() => {
      console.log('outer 3 then');
    })
    .then(() => {
      console.log('outer 4 then');
    });
    
    Promise.all([path1, path2]).then(() => {
      console.log('path1 and path2 finished');
    });
3limin4t0r
  • 19,353
  • 2
  • 31
  • 52
  • Completely agree that we shouldn't rely on the internals of the javascript engines to execute `then()` callbacks of _unrelated_ promise chains in certain order but IMHO, intention behind such questions is to understand promises better and understand what's happening behind the scenes. – Yousaf May 20 '22 at 15:35