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

一篇文章和一道面试题

最近,有篇名为 《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行的顺序有问题。bash

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

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

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

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

总结

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

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

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

相关文章
相关标签/搜索