简体   繁体   中英

JavaScript quiz of printing sequence with combination of promise.then and async function

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.

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 bc
  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 bc
  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 bc
  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 bcd
  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 bcd
  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 bcd 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM