一天惬意的下午。朋友给我分享了一道头条面试题,以下:node
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')
复制代码复制代码
这个题目主要是考察对同步任务、异步任务:setTimeout、promise、async/await的执行顺序的理解程度。(建议你们也本身先作一下o)面试
当时因为我对async、await了解的不是很清楚,答案错的千奇百怪 :(),就不记录了,而后我就去看文章理了理思路。如今写在下面以供往后参考。promise
这里首先须要明白几个概念:同步任务、异步任务、任务队列、microtask、macrotaskbash
总结一下:数据结构
task queue、microtask、macrotask异步
- An event loop has one or more task queues.(task queue is macrotask queue)
- Each event loop has a microtask queue.
- task queue = macrotask queue != microtask queue
- a task may be pushed into macrotask queue,or microtask queue
- when a task is pushed into a queue(micro/macro),we mean preparing work is finished,so the task can be executed now.
因此咱们能够获得js执行顺序是:async
开始 -> 取第一个task queue里的任务执行(能够认为同步任务队列是第一个task queue) -> 取microtask所有任务依次执行 -> 取下一个task queue里的任务执行 -> 再次取出microtask所有任务执行 -> … 这样循环往复函数
macrotask:oop
microtask:post
不记得题的!继续往下看,舒适的准备了题目,不用往上翻
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')
复制代码复制代码
node环境下: 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
按照上面写的js执行顺序就能够获得正确结果,但最后却又存在两个答案,为何会出现两种结果呢?咱们能够看到两种结果中就是async1 end 和 Promise2之间的顺序出现差异,这主要是V8最新版本与稍老版本的差别,他们对await的执行方法不一样,以下:
async function f(){
await p
console.log(1);
}
//新版V8应该会解析成下面这样
function f(){
Promise.resolve(p).then(()=>{
console.log(1)
})
}
//旧版的V8应该会解析成下面的这样
function f(){
new Promise(resolve=>{
resolve(p)
}).then(()=>{
console.log(1)
})
}
复制代码复制代码
正对上面的这两种差别主要是:
不用担忧这个题没解,真相只有一个。根据 TC39 最近决议,await将直接使用 Promise.resolve() 相同语义。
最后咱们以最新决议来分析这个题目的可能的执行过程:
stackoverflow上的一道题目
let resolvePromise = new Promise(resolve => {
let resolvedPromise = Promise.resolve()
resolve(resolvedPromise)
})
resolvePromise.then(() => {
console.log('resolvePromise resolved')
})
let resolvedPromiseThen = Promise.resolve().then(res => {
console.log('promise1')
})
resolvedPromiseThen
.then(() => {
console.log('promise2')
})
.then(() => {
console.log('promise3')
})
复制代码复制代码
结果:promise1 -> promise2 -> resolvePromise resolved -> promise3
这道题真的是很是费解了。为何'resolvePromise resolved'会在第三行才显示呢?和舍友讨论了一夜无果。
其实这个题目的难点就在于resolve一个Promise对象,js引擎会怎么处理。咱们知道Promise.resolve()的参数为Promise对象时,会直接返回这个Promise对象。但当resolve()的参数为Promise对象时,状况会有所不一样:
resolve(resolvedPromise)
//等同于:
Promise.resolve().then(() => resolvedPromise.then(resolve, reject));
复制代码复制代码
因此这里第一次执行到这儿的时候:
() => resolvedPromise.then(resolve, reject)
会被放入当前任务列表的最后结束!这里面的几段代码是比较重要的,解释了js会按照什么样的方式来执行这些新特性。
最后若是有误,欢迎指正。