以下代码均在 nodejs v10.12.0 平台上测试得到结果(浏览器略有差异)

    从一道面试题讲起:

    1. async function async1(){
    2. console.log('async1 start');
    3. await async2();
    4. console.log('async1 end');
    5. }
    6. async function async2() {
    7. console.log('async2');
    8. }
    9. console.log('script start');
    10. setTimeout(function() {
    11. console.log('setTimeout');
    12. }.0)
    13. async1();
    14. new Promise(function(resolve){
    15. console.log('promise1')
    16. resolve();
    17. }).then(function(){
    18. console.log('promise2');
    19. }
    20. console.log('script end');
    21. // script start
    22. // async1 start
    23. // async 2
    24. // promise1
    25. // script end
    26. // promise2
    27. // async1 end
    28. // setTimeout

    涉及的知识点有

    1. JavaScript异步机制(事件循环机制)
    2. 微任务和宏任务

    其中较难理解的一点是:在async1中,等待async2执行完后(打印出了async2),竟然没有继续执行async1中的剩余代码,而是跳出async1,执行其他同步代码(console.log(‘promise1’))

    查了很多博客的解释,有句解释可以帮我很好理解:async函数中的await会跳出当前函数堆栈,交出代码执行权

    await 在等待一个Promise,当它等到Promise后,就将执行权交给其他堆栈,然后继续等待Promise的状态变为fullfill或rejected,当 Promise 的状态从pending 改变后,会将对应的回调函数(resolve或reject)加入 microTask 队列中

    另一个较难理解的是打印完 script end 后 先打印 promise2 再 async1 end;要理解这个问题还是简化下代码

    同步代码执行完后总会去检查任务队列中是否有新的 microTask,有则立即执行所有 microTask

    1. async function async1(){
    2. console.log("async1 start");
    3. await async2();
    4. console.log("async1 end");
    5. }
    6. async function async2(){
    7. console.log("async2");
    8. }
    9. async1();
    10. Promise.resolve().then(()=>{
    11. console.log("promise");
    12. })
    13. // 正确顺序:
    14. // async1 start
    15. // async2
    16. // promise
    17. // async1 end
    1. 首先执行 async1 函数,打印出 “async1 start”;再开始执行 await async2() | logs | async1 start | | :—-: | —- | | tasks | script |

    2. 再执行 async2 函数,打印出 async2 ; async2 返回一个 Promise(此时 Promise 的状态是 pending),并跳出当前async 函数(async1),执行其他同步代码或 microTask
      | logs | async1 start / async2
      | | :—-: | —- | | tasks | script |

    这里贴张firefox控制台的运行结果,显示await 到 pending 的 Promise 后就交出执行权了 微信截图_20191023150911.png

    1. 执行 Promise.resolve() ,因为 Promise.resolve() 返回的是 fullfilled 的 Promise,所以这里的 Promise.then 回调函数被添加入任务队列中 | logs | async1 start / async2 | | :—-: | —- | | tasks | Promise.then |

    2. 回到 async1 中的 await async2 返回的结果 Promise(pending),执行它并等待这个 Promise 的状态变为 fullfilled 后,将该 Promise 的then回调加入任务队列(此时 async2 返回的 Promise 的then回调是空函数,但仍然存在) | logs | async1 start / async2 | | :—-: | —- | | tasks | Promise.then / async2:Promise.then
      |

    3. 现在任务队列中存在两个 microTask,首先执行 Promise.then ,打印 promise | logs | async1 start / async2 / promise
      | | :—-: | —- | | tasks | async2:Promise.then
      |

    4. 再执行 async2:Promise.then,直到这里 await 的等待大业才算结束,执行 async1 余下任务 | logs | async1 start / async2 / promise / async1 end
      | | :—-: | —- | | tasks | (null) |

    可是并非是 await 就会交出代码执行权,如果await后面的函数返回的不是 Promise 对象,则不需交出执行权
    将 async2 函数改为普通函数:

    1. function async2(){
    2. console.log("async2");
    3. }
    4. // async1 start
    5. // async2
    6. // async1 end
    7. // promise

    这时代码等同于同步代码

    所以既然 await 等的是 Promise,那返回 Promise 的普通函数能交出执行权吗?将async2 改为返回值为 Promise 对象:

    1. function async2(){
    2. console.log("async2");
    3. return Promise.resolve();
    4. }
    5. // 结果:
    6. // async1 start
    7. // async2
    8. // promise
    9. // async1 end

    参考资料: 理解 JavaScript 的 async/await 一道async/await、promise和setTimeout的面试题 今日头条async/await面试题执行顺序 一次弄懂Event Loop(彻底解决此类面试问题)