2

I'm confused about the order of executing promise.then with async function. The result of that quiz that shows on the console is: 1, a, 2, b, c, d, 3, e.

I'm wondering:

  • Why 3 just inserts to between d and e?
  • Also, I found that fulfilled with async function would insert to the task queue every three promise.then, but why?
  • Does fulfilled with async function act just like return a Promise.resolve?

Here is my test for those assumptions:

https://codepen.io/allieschen/pen/WNMjgPx

Below is the origin quiz code:

new Promise((resolve) => {
  console.log(1);
  resolve();
  })
  .then(async () => {
    console.log(2);
  })
  .then(async () => {
    console.log(3);
  });

new Promise((resolve) => {
  console.log("a");
  resolve();
  })
  .then(() => {
    console.log("b");
  })
  .then(() => {
    console.log("c");
  })
  .then(() => {
    console.log("d");
  })
  .then(() => {
    console.log("e");
  });

I have used the debugger with Node.js and it shows that from b to e, they went over (got parsed?) the stack continually. And then the program will go back to fulfill the aync function and print 2.

So, I expected to see: 1, a, 2, b, c, d, e, 3.

I'm very grateful for any help with the question.

3
  • im not sure if it helps but putting async keyword before a function is like returning a promise from inside that function. Commented May 19, 2022 at 15:23
  • Why would you use async without a corresponding await? Commented May 19, 2022 at 15:24
  • The simple answer to the quiz is that one sequence is 1, 2, 3 and the other sequence is a, b, c, d, e, and they are arbitrarily interleaved. It doesn't matter, and is in fact subject to change. Commented May 19, 2022 at 21:38

1 Answer 1

2

To answer your questions, let me first try to explain how the code in your question executes.

  1. First promise constructor is executed

    new Promise((resolve) => {
      console.log(1);
      resolve();
    })
    

    logging 1 on the console and resolves the promise synchronously

  2. As the promise is resolved, a job is enqueued in the micro-task queue to execute the fulfilment handler passed to the then() method

    new Promise((resolve) => {
      ...
    })
    .then(async () => {
      console.log(2);
    })
    
    micro-task queue: [ job(console.log(2)) ]
    
    console output: 1
    
  3. Second promise constructor is executed

    new Promise((resolve) => {
      console.log("a");
      resolve();
    })
    

    logging 'a' on the console and resolves the promise synchronously

  4. As the promise is resolved, a job is enqueued in the micro-task queue to execute the fulfilment handler passed to the then() method

    new Promise((resolve) => {
      ...
    })
    .then(async () => {
      console.log("b");
    })
    
    micro-task queue: [ job(console.log(2)), job(console.log('b')) ]
    
    console output: 1 a
    
  5. After the synchronous execution of the script ends, javascript starts processing the micro-task queue

    micro-task queue: [ job(console.log(2)), job(console.log('b')) ]
    
    console output: 1 a
    
  6. First job from the micro-task is dequeued and processed, logging 2 on the console

    micro-task queue: [ job(console.log('b')) ]
    
    console output: 1 a 2
    
  7. As the fulfilment handler passed to the first then() method of the first promise is an async function, fulfilment handler implicitly returns a promise.

    If the return value of the callback function passed to the then method is also a promise, the promise returned by the then() method is resolved to the promise returned by its callback function.

    In other words, fate of the outer promise (returned by the then() method) depends on the inner promise (returned by its callback function). If the inner promise fulfils, outer promise will also fulfil with the same value.

    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);
    });
    

    To resolve the outer promise to the inner promise, another job will be enqueued in the micro-task queue

    micro-task queue: [ job(console.log('b')), job(resolve(outerPr, innerPr) ]
    
    console output: 1 a 2
    
  8. Next job in the micro-task queue is dequeued and processed, logging 'b' on the console

    micro-task queue: [ job(resolve(outerProm, innerProm) ]
    
    console output: 1 a 2 b
    
  9. As the callback that logged 'b' on the console implicitly returned undefined, promise returned by the wrapper then() method is fulfilled with the value of undefined.

    As a result, another job is enqueued in the micro-task queue to execute the fulfilment handler of the next then() method

    new Promise((resolve) => {
      ...
    })
    .then(() => {
      ...
    })
    .then(() => {
      console.log("c");
    })
    
    micro-task queue: [job(resolve(outerProm, innerProm), job(console.log('c')]
    
    console output: 1 a 2 b
    
  10. Next job in the micro-task queue is dequeued and processed. As the next job is related to one promise waiting for another promise to settle (resolve or reject), the inner promise is resolved with the value of undefined (value returned by the corresponding callback function) (See step 7).

    As a result, another job in enqueued in the micro-task queue to execute the fulfilment handler of the inner promise. Fulfilment handler in this case is the resolve function of the outer promise. Calling it will resolve the outer promise

    micro-task queue: [ job(console.log('c'), job(resolve(outerProm), ]
    
    console output: 1 a 2 b
    
  11. Next job in the micro-task queue is dequeued and processed, logging 'c' on the console

    micro-task queue: [ job(resolve(outerProm), ]
    
    console output: 1 a 2 b c
    
  12. As the callback that logged 'c' on the console implicitly returned undefined, promise returned by the wrapper then() method is fulfilled with the value of undefined.

    As a result, another job is enqueued in the micro-task queue to execute the fulfilment handler of the next then() method

    new Promise((resolve) => {
      ...
    })
    .then(() => {
      ...
    })
    .then(() => {
      ...
    })
    .then(() => {
      console.log("d");
    })
    
    micro-task queue: [ job(resolve(outerProm), job(console.log('d') ]
    
    console output: 1 a 2 b c
    
  13. Next job in the micro-task queue is dequeued and processed, resolving the promise returned by the then() method of the first promise

    new Promise((resolve) => {
      ...
    })
    .then(async () => {
      console.log(2);
    })
    

    As a result, a job is enqueued in the micro-task queue to execute its fulfilment handler

    micro-task queue: [ job(console.log('d'), job(console.log(3) ]
    
    console output: 1 a 2 b c
    
  14. Next job in the micro-task queue is dequeued and processed, logging 'd' on the console

    micro-task queue: [ job(console.log(3) ]
    
    console output: 1 a 2 b c d
    
  15. After the execution of callback that logged 'd' on the console, another job is enqueued in the micro-task queue to log 'e' on the console

    micro-task queue: [ job(console.log(3), job(console.log('e') ]
    
    console output: 1 a 2 b c d 
    
  16. Finally the last two job will be dequeued and processed one after the other and the final output will be:

    micro-task queue: [  ]
    
    console output: 1 a 2 b c d 3 e
    

Now coming to your questions:

Why 3 just inserts to between d and e?

See the detailed explanation above.

Also, I found that fulfilled with async function would insert to the task queue every three promise.then, but why?

If you understand the explanation above, you now also understand the output in the codepen.

Does fulfilled with async function act just like return a Promise.resolve?

Yes. As async function implicitly returns a promise, returning Promise.resolve() from the callback function of a then() method will have the same effect (see step 7 above).


Note: Although understanding your code's output is a good thing to understand the language in depth, real-world code shouldn't rely on the timing of promises in two different unrelated promise chains.

Sign up to request clarification or add additional context in comments.

1 Comment

You are a lifesaver. I thought that I understood promise and async function, even to mix them together, though I never do that in real life. However, this quiz and your wonderful explanation really overwhelm me.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.