「今日头条」前端面试题和思路解析

一篇文章和一道面试题

最近,有篇名为 《8张图帮你一步步看清 async/await 和 promise 的执行顺序》 的文章引发了个人关注。前端

做者用一道2017年「今日头条」的前端面试题为引子,分步讲解了最终结果的执行缘由。其中涉及到了很多概念,好比异步的执行顺序,宏任务,微任务等等,同时做者限定了执行范围,以浏览器的 event loop 机制为准。下面是原题的代码:react

async function async1 () {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}

async function async2 () {
    console.log('async2');
}

console.log('script start');

setTimeout(function () {
    console.log('setTimeout');
}, 0);

async1();

new Promise(function (resolve) {
    console.log('promise1');
    resolve();
}).then(function () {
    console.log('promise2');
});

console.log('script end');

紧接着,做者先给出了答案。并但愿读者先行自我测试。面试

script start
async1 start
async2
promise1
script end
promise2
async1 end
setTimeout

我在看这道题的时候,先按照本身的理解写出告终果。chrome

script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout

一些重要的概念

这里须要先简单地说一些 event loop 的概念。segmentfault

  • Javascript是单线程的,全部的同步任务都会在主线程中执行。
  • 主线程以外,还有一个任务队列。每当一个异步任务有结果了,就往任务队列里塞一个事件。
  • 当主线程中的任务,都执行完以后,系统会 “依次” 读取任务队列里的事件。与之相对应的异步任务进入主线程,开始执行。
  • 异步任务之间,会存在差别,因此它们执行的优先级也会有区别。大体分为 微任务(micro task,如:Promise、MutaionObserver等)和宏任务(macro task,如:setTimeout、setInterval、I/O等)。同一次事件循环中,微任务永远在宏任务以前执行。
  • 主线程会不断重复上面的步骤,直到执行完全部任务。

另外,还有 async/await 的概念。promise

  • async 函数,能够理解为是Generator 函数的语法糖。
  • 它创建在promise之上,老是与await一块儿使用的。
  • await会返回一个Promise 对象,或者一个表达式的值。
  • 其目的是为了让异步操做更优雅,能像同步同样地书写。

个人理解

再说说我对这道题的理解。浏览器

  • 首先,从console的数量上看,会输出8行结果。
  • 再瞟了一眼代码,看到了setTimeout,因而,默默地把它填入第8行。
  • 在setTimeout附近,看到了 console.log( 'script start' ) 和 async1(),能够确认它们是同步任务,会先在主线程中执行。因此,妥妥地在第1行填入 script start,第2行填入async1方法中的第一行 async1 start。
  • 接下来,遇到了await。从字面意思理解,让咱们等等。须要等待async2()函数的返回,同时会阻塞后面的代码。因此,第3行填入 async2。
  • 讲道理,await都执行完了,该轮到console.log( 'async1 end' )的输出了。可是,别忘了下面还有个Promise,有一点须要注意的是:当 new 一个 Promise的时候,其 resolve 方法中的代码会当即执行。若是不是 async1()的 await 横插一杠,promise1 能够排得更前面。因此,如今第4行填入 promise1。
  • 再接下来,同步任务 console.log( 'script end' ) 执行。第5行填入 script end。
  • 还有第6和第7行,未填。回顾一下上面提到 async/await 的概念,其目的是为了让异步能像同步同样地书写。那么,我认为 console.log( 'async1 end' ) 就是个同步任务。因此,第6行填入async1 end。
  • 最后,瓜熟蒂落地在第7行填入 promise2。

与做者答案的不一样

回过头对比与做者的答案,发现第6和第7行的顺序有问题。babel

再耐心地往下看文章,反复地看了几遍 async1 end 和 promise2 谁先谁后,仍是没法理解为什么在chrome浏览器中,promise2 会先于 async1 end 输出。异步

而后,看到评论区,发现也有人提出了相同的疑惑。@rhinel提出,在他的72.0.3622.0(正式版本)dev(64 位)的chrome中,跑出来的结果是 async1 end 在 promise2 以前。async

随即我想到了一种可能,JS的规范可能会在将来有变化。因而,我用本身的react工程试了一下(工程中的babel-loader版本为7.1.5。.babelrc的presets设置了stage-3),结果与个人理解一致。当前的最新版本 chromeV71,在这里的执行顺序上,的确存在有问题。

因而,我也在评论区给做者留了言,进行了讨论。@rhinel最后也证明,其实最近才发布经过了这个顺序的改进方案,这篇 《Faster async functions and promises》 详细解释了这个改进,以及实现效果。不久以后,做者也在他文章的最后,补充了咱们讨论的结果,供读者参考。

总结

最后,我想说的是,本文虽然只是由一道面试题引伸出的,对浏览器执行顺序的思考、讨论与验证的过程。但正是由于有了这些过程,才让更多的思想得以碰撞,概念进一步得以理解,规范得以明了。

有机会的话,但愿能有与更多的同道,多多交流。

更新

讲道理,async/await 已经出来挺久了,但在近期的面试中,凡是问及异步操做,面试者的回答都仍是 Promise,甚至知道 async/await 都不多,看来还有待进一步普及。并非说 Promise 有什么很差,只是以为 async/await 用着挺爽的,但愿能有更多的人用吧。只有用了,才能进一步理解,产生更多的思考。

因此,这两天翻出了以前写的关于什么是async函数,及其相较于 Promise 的优点。从新整理了一下,原文请前往《细说async/await相较于Promise的优点》

但愿对你有帮助,也期待进一步的交流,感谢!

PS:欢迎关注个人公众号 “超哥前端小栈”,交流更多的想法与技术。

图片描述

相关文章
相关标签/搜索