以下是一些事件循环机制例子
0.两个例子先测下实力
async function async1 () {
await new Promise((resolve, reject) => { // 右边是Promise无需等待
resolve()
})
console.log('A')
}
async1()
new Promise((resolve) => {
console.log('B')
resolve()
}).then(() => {
console.log('C')
}).then(() => {
console.log('D')
})
// 最终结果👉: B A C D
解析:调用async1,有await等待一个promise没有返回值,跳出,console.log(‘A’)不执行。打印B,有.then把C放入微任务。现在执行await后面的打印A。之后在取出微任务的打印C,最后是D。结果就是B A C D
async function async1 () {
await async2()
console.log('A')
}
async function async2 () {
return new Promise((resolve, reject) => { // 从async视角等待2个then
resolve()
})
}
async1()
new Promise((resolve) => {
console.log('B')
resolve()
}).then(() => {
console.log('C')
}).then(() => {
console.log('D')
})
// 最终结果👉: B C D A
解析:执行async1, async1中await了async2, async2返回了Promise但是也是没有明确的值,跳出,console.log(‘A’)不执行。来到下面打印B,之后微任务放入C,(解释一下这个为什么没有打印A,跟上面哪里不一样?)取出微任务中的,打印C,之后打印D,最后打印A。⚠️这里与上面输出不同是因为,第二个
await
后面跟的是一个async函数
,是async函数就要根据它返回值的类型进行不同数目的微任务
第二个例子中async2返回了一个Promise,根据结论,返回值为一个Promise的需要等待 2个then的时间,那么等待then©、then(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; // 非thenable、非promise
}
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) { // thenable
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) => { // promise
resolve()
})
}
testC().then(() => console.log(1));
Promise.resolve()
.then(() => console.log(2))
.then(() => console.log(3));
// (等待两个then)最终结果👉: 2 3 1
async function testC () {
return new Promise((resolve, reject) => { // promise
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 1 3 4 ,这里的promise需要等待2个then的时间。7也没问题。遇见.then8放进微任务, 打印11。从微任务队列取出打印8,9放进微任务,取出打印9,10放进微任务。至此等待的2个then已经完成。回到async1中将await async2()
后面的打印AAA。取出打印10,最后是宏任务setTimeout中的6。
步骤拆分👇:
0.先执行同步代码,输出5
1.执行setTimeout
,是放入宏任务异步队列中
2.接着执行async1
函数,输出1
3.执行async2
函数,输出3
4.Promise
构造器中代码属于同步代码,输出4
async2
函数的返回值是Promise
,等待2个then
后放行,所以AAA
暂时无法输出5.
async1
函数暂时结束,继续往下走,输出7
6.同步代码,输出11
7.执行第一个then
,输出8
8.执行第二个then
,输出9
9.终于等到了两个then
执行完毕,执行async1
函数里面剩下的,输出AAA
10.再执行最后一个微任务then
,输出10
11.执行最后的宏任务setTimeout
,输出6
2. await 右值类型区别
2.1、非 thenable(会立即向微任务队列添加一个微任务then,但不需等待)
🌰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 // 非 thenable,无需等待
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
Note:
await
后面接非 thenable
类型,会立即向微任务队列添加一个微任务then
,但不需等待
2.2、thenable类型(等待一个then
的时间之后执行)
async function test () {
console.log(1);
await {
then (cb) { // thenable需等待一个then时间
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 7
Note:
await
后面接thenable
类型,需要等待一个then
的时间之后执行
2.3、Promise类型(同非thenable无需等待)
async function test () {
console.log(1);
await new Promise((resolve, reject) => { // 无需等待,但正常是2个then时间
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, 但在这次更新中并没有修改
thenable
的情况
这样做可以极大的优化 await 等待的速度👇
async function func () {
console.log(1);
await 1; // 非 thenable,无需等待1个then
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 11
前面5 1 7没问题,正常要从微任务对列中拿出8,但是await func();中await 1; 非 thenable,无需等待1个then,所以先打印出了2。之后走的打印8,放入9。本来要打印9的,但是又到了await 2; 非 thenable,无需等待1个then,所以先打印出了3。之后取出打印9。以此类推,到await func();全部执行完。打印6,最后是11。
Note:
await
和Promise.prototype.then
虽然很多时候可以在时间顺序上能等效,但是它们之间有本质的区别。
1.
test
函数中的await
会等待func
函数中所有的await
取得 恢复函数执行 的命令并且整个函数执行完毕后才能获得取得 恢复函数执行的命令;
2.test
函数的await
此时不能在时间的顺序上等效then
,而要等待到func
函数完全执行完毕;
3.比如这里的数字6
很晚才输出,如果单纯看成then
的话,在下一个微任务队列执行时6
就应该作为同步代码输出了才对。
所以我们可以合并两个函数的代码👇
async function test () {
console.log(5);
// 原来func()中的
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) => { // async返回值是非thenable,无需等待
resolve()
})
}
async function async3 () {
return new Promise((resolve, reject) => { // async返回值是promise需要等待2个then
resolve()
})
}
async function async1 () {
// 方式一:最终结果:B A C D
// await new Promise((resolve, reject) => { // 同非thenable无需等待,最新规定已经移除了等待2个then
// 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() //拿到确切的func函数返回值undefined
// .then(() => console.log(5))
// .then(() => console.log(6))
// .then(() => console.log(7))
// 方式二:1 2 4 5 6 7 3
return Promise.resolve() // 拿到func函数返回值,但是并未获得具体的结果
.then(() => console.log(5))
.then(() => console.log(6))
.then(() => console.log(7))
}
async function test () {
console.log(1);
await func(); // 方式一 拿到确切值undefined等待了一个then
console.log(3);
}
test();
console.log(4);
步骤拆分👇:
方式一:
同步代码输出1
、2
,接着将log(5)
处的then1
加入微任务队列,await
拿到确切的func
函数返回值undefined
,将后续代码
console.log(3);
放入微任务队列(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() // 没拿到确切值,await一直等到Promise.resolve().then...执行完成
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 3
这里也是懵
Note:
综上,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
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
其他例子:
setTimeout+Promise+Async输出顺序?很简单呀!
4.总结
4.1 async函数返回值
📑结论:async
函数在抛出返回值时,会根据返回值类型开启不同数目的微任务
return结果值:非thenable
、非promise
(不等待)
return结果值:thenable
(等待 1个then
的时间)
return结果值:promise
(等待 2个then
的时间)
4.2 await右值类型区别
- 接非
thenable
类型,会立即向微任务队列添加一个微任务then
,但不需等待 - 接
thenable
类型,需要等待一个then
的时间之后执行 - 接
Promise
类型(有确定的返回值),会立即向微任务队列添加一个微任务then
,但不需等待- TC 39 对
await
后面是promise
的情况如何处理进行了一次修改,移除了额外的两个微任务,在早期版本,依然会等待两个then
的时间
- TC 39 对
作者:Squirrel_
链接:https://juejin.cn/post/7194744938276323384
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。