最开始在群里看到一道面试题
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')
从各个浏览器运行结果不一样开始
在群里争论了一下,关于在Firefox和Chrome的上运行结果不同的问题。
Chrome

火狐

疑惑在于async1 end和promise2的顺序?
因为不同版本v8引擎对对Promise和Async/Await的处理不同 ,那么这个不同是什么
事实上这个顺序的差异不在于我开始所想的关于eventloop的问题。而是
await 后面是promise 的话,到底发生了什么?
尝试解答
如果用'自动执行器'和Generator 函数模拟一个async。
模拟可能是在火狐在这里一个执行情况。
首先async返回的是一个promise
那么这里模拟的async
const asyncFn = function (fn) {
return Promise.resolve(spawn(fn))
}
是吧?那么我还需要一个自动执行器,
function spawn (gen){
return new Promise((resolve,reject)=>{
let g = gen()
function step(nextF){
try{
next = nextF()
}catch(e){
reject(e)
}
if (next.done) {
return resolve(next.value);
}
new Promise(resolve=>{
resolve(next.value) // Firefox
}).then(val=>{
step(()=>{
return g.next(val)
})
},
e=>{
step(()=>{
return g.next(e)
})
})
}
step(()=>{
return g.next(undefined)
})
})
}
现在把题目里的async全部替换一下,把setTimeout删掉,代码就等同于下面的。
const async1 = function* () {
console.log('async1 start')
yield asyncFn(async2)
console.log('async1 end')
}
const asyncFn = function (fn) {
return Promise.resolve(spawn(fn))
}
function * async2() {
console.log('async2')
}
asyncFn(async1)
new Promise(function(resolve) {
console.log('promise1')
resolve()
}).then(function() {
console.log('promise2')
}).then(function() {
console.log('promise3')
})
所以这些问题原因就出在spawn里面的
new Promise(resolve=>{
resolve(next.value) // firefox
})
这里的next.value是一个promise,这样子就相当于用yield asyncFn(async2)
和上面的代码实现await asyncFn(async2)。试图更好的解释await promise 这里到底发生了什么?
这就相当于是在说resolve(next.value)发生了什么?
先看下面一段代码
let p1 = function (){
return new Promise(resolve=>resolve())
}
new Promise(resolve=>resolve(p1())).then(()=>{ //当p1为promise时
console.log('first')
})
new Promise(resolve=>resolve()).then(()=>{
console.log('second')
})
//"second
// "first" 延迟了
这种的'延时'在于 resolve(thenable)和resolve('non-thenable-object')的差异。
根据文章作者的内容,spawn里面的代码可以改写为
new Promise(resolve=>{
Promise.resolve(next.value).then(()=>{
Promise.resolve().then(()=>resolve())
})
})
这样子,job队列不会先立即执行resolve(),
如果把上面代码加两个console.log
new Promise(resolve=>{
Promise.resolve(next.value).then(()=>{
console.log('第一个')
Promise.resolve().then(()=>{
console.log('第二个')
resolve()})
})
})

- 执行
new Promise(resolve=>{//.....}),第一层的promise.then进入微任务Event Queue - 执行
pormise1,后面的第一个promise.then进入微任务Event Queue - 第一轮事件循环宏任务执行完,准备执行微任务队列里的任务
- 执行微任务Event Queue里的第一个
microtask,打印出 '第一个',下一层promise.then进入队列。 - 执行然后打印出'promise2',
promise3进入队列 - 执行然后打印'第二个',后面的
promise.then加入队列 - 执行然后打印'promise3'
- 执行
step(()=>{return g.next(val)}),打印'async1 end',async1执行完毕,done:true。
这里面延迟两个microtask,这也是题目在火狐和Chrome73以前版本输出结果async1 end在后面的原因。
同样的
//chrome 73
Promise.resolve(next.value)
这种结果和在chrome 73里面是一致的,Promise.resolve
并不会产生一个新 promise,而是直接返回next.value,这当然就立即执行了。
最后对比一下
//chrome 73
Promise.resolve(next.value)//立即执行
//Firefox和Chrome 70
new Promise(resolve=>{resolve(next.value)}) //延迟两个
平常认为是同样的东西,在这里的结果却完全不一样。
一些结论
- 其实就是规范的问题
- 在这里面的差异可能在于
Promise.resolve()和new Promise(resolve=>resolve())。实际上根据规范他们并不能完全相等。
最后
` 如果觉得有不对的地方,希望可以留言指正交流,同时你觉得有说的不明白或者不完善的地方,可以看参考文章。
参考文章
- github.com/tc39/ecma26…
- www.zhihu.com/question/26…
- segmentfault.com/q/101000001…
- segmentfault.com/q/101000001…
- github.com/xianshenglu…
- stackoverflow.com/questions/5…
- v8.js.cn/blog/fast-a…