今天给大家带来一道出现频率很高的面试题,最初是在头条的面试中出现,主要考察大家对 event loop
、Promise
、async/await
等知识点的掌握情况。
请写出下面代码的输出结果
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')
千万不要看到 async 就害怕,其实它并不神秘。
在解析这道题目之前,希望大家能先理解以下几个知识点:
MutationObserver
、Promise.then()
或catch()
、Promise
为基础开发的其它技术,比如fetch API
、V8的垃圾回收过程
、Node独有的process.nextTick
。script
、setTimeout
、setInterval
、setImmediate
、I/O
、UI rendering
。注意:在所有任务开始的时候,由于宏任务中包括了script,所以浏览器会先执行一个宏任务,在这个过程中你看到的延迟任务(例如setTimeout)将被放到下一轮宏任务中来执行。
在这里也不赘述 async/await 的基础内容,大家需要知道:
基于以上知识点,我们来进行题目的分析:
script start
,并向下执行,遇见 setTimeout
,将其回调放入宏任务当中async1 start
,继续下后执行到 await async2()
,执行 async2
函数async2
,按照 async 函数规则,async2 函数仍然返回一个 promise,作为 async1 函数中的 await 表达式的值。相当于:Promise.resolve().then(() => {})
script end
Promise.resolve().then(() => {})
其实什么也没做。但这时候 await 中断失效,继续执行 async1 函数,输出 async1 end
promise2
setTimeout
我将代码重新拷贝,加上注释,我们再来回顾一下:
async function async1() {
console.log('async1 start') // step 4: 直接打印同步代码 async1 start
await async2() // step 5: 遇见 await,首先执行其右侧逻辑,并在这里中断 async1 函数
console.log('async1 end') // step 11: 再次回到 async1 函数,await 中断过后,打印代码 async1 end
}
async function async2() {
console.log('async2') // step 6: 直接打印同步代码 async2,并返回一个 resolve 值为 undefined 的 promise
}
console.log('script start') // step 1: 直接打印同步代码 script start
// step 2: 将 setTimeout 回调放到宏任务中,此时 macroTasks: [setTimeout]
setTimeout(function() {
console.log('setTimeout') //step 13: 开始执行宏任务,输出 setTimeout
}, 0)
async1() // step 3: 执行 async1
// step 7: async1 函数已经中断,继续执行到这里
new Promise(function(resolve) {
console.log('promise1') // step 8: 直接打印同步代码 promise1
resolve()
}).then(function() { // step 9: 将 then 逻辑放到微任务当中
console.log('promise2') // step 12: 开始执行微任务,输出 promise2
})
console.log('script end') // step 10: 直接打印同步代码 script end,并回到 async1 函数中继续执行
最后的输出顺序是:
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
PS:这道题最后的async1 end
和 promise2
的顺序其实存在争议,与浏览器实现或者Node.js的版本有关。
由上述例题可见,这一类面试常见的「必考题」灵活多变,且会受到语言规范以及浏览器实现的影响。虽然有些考察点「涉嫌」「刁难」面试者,但是掌握最基本的异步理论、清楚规范要求细节,确实是能够灵活运用的关键,也是能够避免或追查 bugs 的必备知识。
我对大家的建议是,对于这些内容不必头大,见一个分析一个,分析一个就「死记」一个,规范永远没有为什么,但是仔细思考却总有它的道理。不然你们想想,JavaScript 为什么一开始就是单线程异步的?
《前端面试题宝典》
经过近一年的迭代,现已推出 小程序
和 电脑版刷题网站 (https://fe.ecool.fun/
),欢迎大家使用~
同时,我们还推出了面试辅导的增值服务,可以为大家提供 “简历指导” 和 “模拟面试” 服务,现在参与还有额外优惠,感兴趣的同学可以联系小助手(微信号:interview-fe)进行体验哦~