跳到主要内容

V8对await的激进优化

async/await中的某些诡异的执行顺序

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

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

async1();

new Promise((resolve) => {
console.log(1)
resolve()
}).then(() => {
console.log(2)
}).then(() => {
console.log(3)
}).then(() => {
console.log(4)
})

上面这段代码以前chrome71执行的结果:

async1 start
async2
1
2
3
async1 end
4

现在执行的结果:

async1 start
async2
1
async1 end
2
3
4

不同执行结果的原因: 新版本的V8采用了激进优化,这原本是nodejs的一个bug,最后发现这个bug的激进优化竟然可以带来性能提升,所以采用了这个改进方案。

“激进优化”,是指await v在语义上将等价于Promise.resolve(v),而不再是现在的new Promise(resolve => resolve(v))

老版的等价代码

function async1(){
console.log('async1 start')
return new Promise(resolve => resolve(async2()))
.then(() => {
console.log('async1 end')
});
}

function async2(){
console.log('async2');
return Promise.resolve();
}

async1();

new Promise((resolve) => {
console.log(1)
resolve()
}).then(() => {
console.log(2)
}).then(() => {
console.log(3)
}).then(() => {
console.log(4)
})

resolve()的转化

resolve 一个 thenable 对象

  • 对于一个对象o,如果o.then是一个function,那么o就可以被称为thenable对象
  • “在 Promise 中 resolve 一个 thenable 对象”,需要先将 thenable 转化为 Promsie,然后立即调用 thenable 的 then 方法
  • 并且这个过程需要作为一个 job 加入微任务队列,以保证对 then 方法的解析发生在其他上下文代码的解析之后

PromiseResolveThenableJob:浏览器对new Promise(resolve => resolve(thenable))的处理 Promise resolve 的是async2(),而async2()返回了一个状态为<resolved>: undefined的 Promsie,Promise 是一个 thenable 对象

let thenable = {
then(resolve, reject) {
console.log('in thenable');
resolve(100);
}
};

new Promise((r) => {
console.log('in p0');
r(thenable);
})
.then(() => { console.log('thenable ok') })

new Promise((r) => {
console.log('in p1');
r();
})
.then(() => { console.log('1') })
.then(() => { console.log('2') })
.then(() => { console.log('3') })
.then(() => { console.log('4') });

结果:

in p0
in p1
in thenable
1
thenable ok
2
3
4

需要注意的是执行完第一个同步任务后注册了一个thenable里的微任务, 所以执行完所以同步任务后首先执行的是in thenable,推迟了一个时序

resolve 一个 Promise 实例

  • promise是一个thenable对象
  • 创建一个 PromiseResolveThenableJob 去处理这个 Promise 实例,这是一个微任务
  • Promise 实例 内部resolve 又会注册这个promise的then方法到微任务

额外创建了两个Job,表现上就是后续代码被推迟了2个时序

所以thenable是promise的时候:

new Promise((resolve) => {
resolve(thenable)
})

时序上等价于:

new Promise((resolve) => {
Promise.resolve().then(() => {
thenable.then(resolve)
})
})

所以老版的最终代码可已转化为:

function async1(){
console.log('async1 start');
const p = async2();
return new Promise((resolve) => {
Promise.resolve().then(() => {
p.then(resolve)
})
})
.then(() => {
console.log('async1 end')
});
}

function async2(){
console.log('async2');
return Promise.resolve();
}

async1();

new Promise((resolve) => {
console.log(1)
resolve()
}).then(() => {
console.log(2)
}).then(() => {
console.log(3)
}).then(() => {
console.log(4)
})

新版的等价代码

采用激进优化后是await v在语义上将等价于Promise.resolve(v),而不再是现在的new Promise(resolve => resolve(v)) Promise.resolve(v)和new Promise(resolve => resolve(v))的区别:

  • v如果是Promise,就不再会去跑一遍这个Promise,直接将返回这个 promise 题中的代码可做如下等价转换:
function async1(){
console.log('async1 start');
const p = async2();
return Promise.resolve(p)// 这里直接返回p不做任何处理
.then(() => {
console.log('async1 end')
});
}
function async2(){
console.log('async2');
return Promise.resolve();
}
async1();
new Promise((resolve) => {
console.log(1)
resolve()
}).then(() => {
console.log(2)
}).then(() => {
console.log(3)
}).then(() => {
console.log(4)
})
async1 start
async2
1
async1 end
2
3
4