本文由 简悦 SimpRead 转码, 原文地址 mp.weixin.qq.com
0、前言
关于promise、async/await的使用相信很多小伙伴都比较熟悉了,但是提到事件循环机制输出结果类似的题目,你敢说都会?
试一试?
🌰1:
async function async1 () { await new Promise((resolve, reject) => { resolve() }) console.log('A')}async1()new Promise((resolve) => { console.log('B') resolve()}).then(() => { console.log('C')}).then(() => { console.log('D')})// 最终结果👉: B A C D🌰2:
async function async1 () { await async2() console.log('A')}async function async2 () { return new Promise((resolve, reject) => { resolve() })}async1()new Promise((resolve) => { console.log('B') resolve()}).then(() => { console.log('C')}).then(() => { console.log('D')})// 最终结果👉: B C D A❓基本一样的代码为什么会出现差别,话不多说👇
1、async 函数返回值
在讨论 await 之前,先聊一下 async 函数处理返回值的问题,它会像 Promise.prototype.then 一样,会对返回值的类型进行辨识。
👉根据返回值的类型,引起 js引擎 对返回值处理方式的不同
📑结论:
async函数在抛出返回值时,会根据返回值类型开启不同数目的微任务
return 结果值:非
thenable、非promise(不等待)return 结果值:
thenable(等待 1 个then的时间)return 结果值:
promise(等待 2 个then的时间)
🌰1:
async function testA () { return 1;}testA().then(() => console.log(1));Promise.resolve() .then(() => console.log(2)) .then(() => console.log(3));// (不等待)最终结果👉: 1 2 3🌰2:
async function testB () { return { then (cb) { cb(); } };}testB().then(() => console.log(1));Promise.resolve() .then(() => console.log(2)) .then(() => console.log(3));// (等待一个then)最终结果👉: 2 1 3🌰3:
async function testC () { return new Promise((resolve, reject) => { resolve() })}testC().then(() => console.log(1));Promise.resolve() .then(() => console.log(2)) .then(() => console.log(3)); // (等待两个then)最终结果👉: 2 3 1async function testC () { return new Promise((resolve, reject) => { resolve() })} testC().then(() => console.log(1));Promise.resolve() .then(() => console.log(2)) .then(() => console.log(3)) .then(() => console.log(4))// (等待两个then)最终结果👉: 2 3 1 4看了这三个🌰是不是对上面的结论有了更深的认识?
稍安勿躁,来试试一个经典面试题👇
async function async1 () { console.log('1') await async2() console.log('AAA')}async function async2 () { console.log('3') return new Promise((resolve, reject) => { resolve() console.log('4') })}console.log('5')setTimeout(() => { console.log('6')}, 0);async1()new Promise((resolve) => { console.log('7') resolve()}).then(() => { console.log('8')}).then(() => { console.log('9')}).then(() => { console.log('10')})console.log('11')// 最终结果👉: 5 1 3 4 7 11 8 9 AAA 10 6👀做错了吧?
哈哈没关系
步骤拆分👇:
先执行同步代码,输出
5执行
setTimeout,是放入宏任务异步队列中接着执行
async1函数,输出1执行
async2函数,输出3
Promise构造器中代码属于同步代码,输出4
async2函数的返回值是Promise,等待2个then后放行,所以AAA暂时无法输出
async1函数暂时结束,继续往下走,输出7同步代码,输出
11执行第一个
then,输出8执行第二个
then,输出9终于等到了两个
then执行完毕,执行async1函数里面剩下的,输出AAA再执行最后一个微任务
then,输出10执行最后的宏任务
setTimeout,输出6
❓是不是豁然开朗,欢迎点赞收藏!
2、await 右值类型区别
2.1、非 thenable
🌰1:
async function test () { console.log(1); await 1; console.log(2);}test();console.log(3);// 最终结果👉: 1 3 2🌰2:
function func () { console.log(2);}async function test () { console.log(1); await func(); console.log(3);}test();console.log(4);// 最终结果👉: 1 2 4 3🌰3:
async function test () { console.log(1); await 123 console.log(2);}test();console.log(3);Promise.resolve() .then(() => console.log(4)) .then(() => console.log(5)) .then(() => console.log(6)) .then(() => console.log(7));// 最终结果👉: 1 3 2 4 5 6 7Note:
await后面接非thenable类型,会立即向微任务队列添加一个微任务then,但不需等待
2.2、thenable类型
async function test () { console.log(1); await { then (cb) { cb(); }, }; console.log(2);}test();console.log(3);Promise.resolve() .then(() => console.log(4)) .then(() => console.log(5)) .then(() => console.log(6)) .then(() => console.log(7));// 最终结果👉: 1 3 4 2 5 6 7Note:
await后面接thenable类型,需要等待一个then的时间之后执行
2.3、Promise类型
async function test () { console.log(1); await new Promise((resolve, reject) => { resolve() }) console.log(2);}test();console.log(3);Promise.resolve() .then(() => console.log(4)) .then(() => console.log(5)) .then(() => console.log(6)) .then(() => console.log(7));// 最终结果👉: 1 3 2 4 5 6 7❓为什么表现的和非 thenable 值一样呢?为什么不等待两个 then 的时间呢?
Note:
TC 39(ECMAScript 标准制定者) 对
await后面是promise的情况如何处理进行了一次修改,移除了额外的两个微任务,在早期版本,依然会等待两个then的时间有大佬翻译了官方解释:更快的 async 函数和 promises[1],但在这次更新中并没有修改
thenable的情况
这样做可以极大的优化 await 等待的速度👇
async function func () { console.log(1); await 1; console.log(2); await 2; console.log(3); await 3; console.log(4);}async function test () { console.log(5); await func(); console.log(6);}test();console.log(7);Promise.resolve() .then(() => console.log(8)) .then(() => console.log(9)) .then(() => console.log(10)) .then(() => console.log(11));// 最终结果👉: 5 1 7 2 8 3 9 4 10 6 11Note:
await和Promise.prototype.then虽然很多时候可以在时间顺序上能等效,但是它们之间有本质的区别。
test函数中的await会等待func函数中所有的await取得 恢复函数执行 的命令并且整个函数执行完毕后才能获得取得 恢复函数执行的命令;也就是说,
func函数的await此时不能在时间的顺序上等效then,而要等待到test函数完全执行完毕;比如这里的数字
6很晚才输出,如果单纯看成then的话,在下一个微任务队列执行时6就应该作为同步代码输出了才对。
所以我们可以合并两个函数的代码👇
async function test () { console.log(5); console.log(1); await 1; console.log(2); await 2; console.log(3); await 3; console.log(4); await null; console.log(6);}test();console.log(7);Promise.resolve() .then(() => console.log(8)) .then(() => console.log(9)) .then(() => console.log(10)) .then(() => console.log(11));// 最终结果👉: 5 1 7 2 8 3 9 4 10 6 11因为将原本的函数融合,此时的 await 可以等效为 Promise.prototype.then,又完全可以等效如下代码👇
async function test () { console.log(5); console.log(1); Promise.resolve() .then(() => console.log(2)) .then(() => console.log(3)) .then(() => console.log(4)) .then(() => console.log(6))}test();console.log(7);Promise.resolve() .then(() => console.log(8)) .then(() => console.log(9)) .then(() => console.log(10)) .then(() => console.log(11));// 最终结果👉: 5 1 7 2 8 3 9 4 10 6 11以上三种写法在时间的顺序上完全等效,所以你 完全可以将 await 后面的代码可以看做在 then 里面执行的结果,又因为 async 函数会返回 promise 实例,所以还可以等效成👇
async function test () { console.log(5); console.log(1);}test() .then(() => console.log(2)) .then(() => console.log(3)) .then(() => console.log(4)) .then(() => console.log(6))console.log(7);Promise.resolve() .then(() => console.log(8)) .then(() => console.log(9)) .then(() => console.log(10)) .then(() => console.log(11));// 最终结果👉: 5 1 7 2 8 3 9 4 10 6 11可以发现,test 函数全是走的同步代码...
所以👉:**async/await 是用同步的方式,执行异步操作 **
3、🌰
🌰1:
async function async2 () { new Promise((resolve, reject) => { resolve() })}async function async3 () { return new Promise((resolve, reject) => { resolve() })}async function async1 () { // 方式一:最终结果:B A C D // await new Promise((resolve, reject) => { // resolve() // }) // 方式二:最终结果:B A C D // await async2() // 方式三:最终结果:B C D A await async3() console.log('A')}async1()new Promise((resolve) => { console.log('B') resolve()}).then(() => { console.log('C')}).then(() => { console.log('D')})大致思路👇:
首先,**
async函数的整体返回值永远都是Promise,无论值本身是什么 **方式一:
await的是Promise,无需等待方式二:
await的是async函数,但是该函数的返回值本身是 ** 非thenable**,无需等待方式三:
await的是async函数,且返回值本身是Promise,需等待两个then时间
🌰2:
function func () {
console.log(2);
// 方式一:1 2 4 5 3 6 7
// Promise.resolve()
// .then(() => console.log(5))
// .then(() => console.log(6))
// .then(() => console.log(7))
// 方式二:1 2 4 5 6 7 3
return Promise.resolve()
.then(() => console.log(5))
.then(() => console.log(6))
.then(() => console.log(7))
}
async function test () {
console.log(1);
await func();
console.log(3);
}
test();
console.log(4);步骤拆分👇:
方式一:
同步代码输出
1、2,接着将log(5)处的then1加入微任务队列,await拿到确切的func函数返回值undefined,将后续代码放入微任务队列(then2,可以这样理解)执行同步代码输出
4,到此,所有同步代码完毕执行第一个放入的微任务
then1输出5,产生log(6)的微任务then3执行第二个放入的微任务
then2输出3然后执行微任务
then3,输出6,产生log(7)的微任务then4执行
then4,输出7方式二:
同步代码输出
1、2,await拿到func函数返回值,但是并未获得具体的结果(由Promise本身机制决定),暂停执行当前async函数内的代码(跳出、让行)输出
4,到此,所有同步代码完毕
await一直等到Promise.resolve().then...执行完成,再放行输出3
方式二没太明白❓
继续👇
function func () {
console.log(2);
return Promise.resolve()
.then(() => console.log(5))
.then(() => console.log(6))
.then(() => console.log(7))
}
async function test () {
console.log(1);
await func()
console.log(3);
}
test();
console.log(4);
new Promise((resolve) => {
console.log('B')
resolve()
}).then(() => {
console.log('C')
}).then(() => {
console.log('D')
})
// 最终结果👉: 1 2 4 B 5 C 6 D 7 3还是没懂?
继续👇
async function test () { console.log(1); await Promise.resolve() .then(() => console.log(5)) .then(() => console.log(6)) .then(() => console.log(7)) console.log(3);}test();console.log(4);new Promise((resolve) => { console.log('B') resolve()}).then(() => { console.log('C')}).then(() => { console.log('D')})// 最终结果👉: 1 4 B 5 C 6 D 7 3Note:
综上,
await一定要等到右侧的表达式有确切的值才会放行,否则将一直等待(阻塞当前async函数内的后续代码),不服看看这个👇
function func () { return new Promise((resolve) => { console.log('B') // resolve() 故意一直保持pending })}async function test () { console.log(1); await func() console.log(3);}test();console.log(4);// 最终结果👉: 1 B 4 (永远不会打印3)// ---------------------或者写为👇-------------------async function test () { console.log(1); await new Promise((resolve) => { console.log('B') // resolve() 故意一直保持pending }) console.log(3);}test();console.log(4);// 最终结果👉: 1 B 4 (永远不会打印3)
🌰3:
async function func () { console.log(2); return { then (cb) { cb() } }}async function test () { console.log(1); await func(); console.log(3);}test();console.log(4);new Promise((resolve) => { console.log('B') resolve()}).then(() => { console.log('C')}).then(() => { console.log('D')})// 最终结果👉: 1 2 4 B C 3 D步骤拆分👇:
同步代码输出
1、2
await拿到func函数的具体返回值thenable,将当前async函数内的后续代码放入微任务then1(但是需要等待一个then时间)同步代码输出
4、B,产生log(C)的微任务then2由于
then1滞后一个then时间,直接执行then2输出C,产生log(D)的微任务then3执行原本滞后一个
then时间的微任务then1,输出3执行最后一个微任务
then3输出D
4、总结
async函数返回值
📑结论:
async函数在抛出返回值时,会根据返回值类型开启不同数目的微任务return 结果值:非
thenable、非promise(不等待)return 结果值:
thenable(等待 1 个then的时间)return 结果值:
promise(等待 2 个then的时间)
await右值类型区别
接非
thenable类型,会立即向微任务队列添加一个微任务then,但不需等待接
thenable类型,需要等待一个then的时间之后执行接
Promise类型 (有确定的返回值),会立即向微任务队列添加一个微任务then,但不需等待TC 39 对
await后面是promise的情况如何处理进行了一次修改,移除了额外的两个微任务,在早期版本,依然会等待两个then的时间
参考资料
[1]
https://juejin.cn/post/6844903715342647310#heading-3: _https://juejin.cn/post/6844903715342647310#heading-3_
作者: Squirrel_