Promise ,async ,await
在稀土掘金上看到一篇关于async,await,promise的帖子.里面讲async,await以及Promise,一反我之前对事件循环的理解,文章中讲述这些会等待不同的时间周期.(我记得事件循环不是微任务FIFO嘛).于是进行了验证.也提出了一些自圆其说的假设.
文章也会按照帖子的顺序逐个解读,使用的环境是
- Tyepscript Playerground
Chrome自带的console
主要集中在 async,await 以及 Promise形成的微任务中
前言
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
这个和async 函数里的Promise没有关系,和之前我的理解一致
- 首先执行
async1函数,由于async1函数里有await,之后的代码相当于注册一个微任务.(B) - 之后的
Promise首先执行同步代码,之后注册两个微任务 - 宏任务结束,微任务队列要清空,且按照注册顺序执行(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
这里我们先按下不表
async
1
async function testA () {
return 1;
}
testA().then(() => console.log(1));
Promise.resolve()
.then(() => console.log(2))
.then(() => console.log(3));
// 1 2 3
我们知道,对于一个async函数,会将他的返回值包裹成有then函数的Promise对象,而then是一个注册微任务的函数,所以这里依次注册 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));
// 2 1 3
就是这里,给我看蒙了,这返回的是啥一个自定义的thenable对象,于是我测试了一下
async function testB() {
return {
then(cb: any) {
cb()
console.log("4")
}
};
}
testB().then(() => console.log(1))
Promise.resolve()
.then(() => console.log(2))
.then(() => console.log(3));
// 4 2 1 3
(这里typescript会显示报错,说 async不应该返回一个带有then函数的对象)
按照我开始的肤浅理解,这里的cb应该指的是我传的函数,那么应该是4和1同时出现.于是我仔细观察了一下这个cb函数.
async function testB() {
return {
then(cb: any) {
console.log(cb)
}
};
}
testB().then(()=>{console.log(1)})
// function () { [native code] }
您瞧怎么找,它打印了引擎中自己带的代码,这说明他执行的根本不是我们给他的,给他的应该这么打印
function testB() {
return {
then(cb: any) {
console.log(cb)
}
};
}
testB().then(()=>{console.log(1)})
// () => { console.log(1); }
(这个时候typescript也不报错了)
那么这个then里cb函数到底是啥,我倾向于是注册微任务的函数,结构类似于下面
async function testB() {
return {
then(cb: any) {
Promise.resolve().then(()=>{console.log("should be 1 position")})
// cb()
console.log(4)
}
};
}
testB().then(() => console.log(1))
Promise.resolve()
.then(() => console.log(2))
.then(() => console.log(3));
// 4 2 "should be 1 position" 3
执行的时候发现你返回的对象有then函数,那么就把自己的then函数作为你的参数,让你去调用它.
那么上述的解释就是
-
我注册了一个函数(
()=> console.log(1)),但是被发现里面有then方法,被改造了,执行的时候被替换成了下面的(下面是我认为的处理),它注册了一个注册微任务的微任务// 发现返回的有 then() ,假设这个函数叫 customThen() Promise.prototype.then=function(cb)=>{ const callback=function(){ Promise.prototype.then.bind(this)(cb) } customThen(callback) } -
注册打印2的微任务.
-
宏任务执行结束,开始清空微任务
-
执行注册微任务的微任务,即注册打印1的微任务
-
打印2
-
上面打印2的微任务的任务结束,注册打印3的微任务
-
打印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));
// 2 3 1
其实async本身就会包装成一个Promise,所以我尝试了将async去掉
function testC () {
return new Promise<void>((resolve, reject) => {
resolve()
})
}
testC().then(() => console.log(1));
Promise.resolve()
.then(() => console.log(2))
.then(() => console.log(3));
// 1 2 3
这里就和平常的一样了
那么为什么两者不一样呢,我猜测可能是Promise包装了多个,导致了跟上面customThenable一样的问题.而我记得对于Promise多个嵌套时,源码里有处理,直接获得最里面的非Promise元素.
所以第一个例子可以理解为
const promiseA = new Promise((resolve, reject) => {
resolve(new Promise((resolve, reject) => {
resolve(4)
}))
})
promiseA.then(res => { console.log(1);console.log(res) })
Promise.resolve()
.then(() => console.log(2))
.then(() => console.log(3));
// 2 3 1 4
Promise是嵌套注册的,但是最终结果是穿透的
await
这里补充一个我之前学习到的await的处理思路
async function test1(){
console.log(1)
await async2()
console.log(2)
}
test1()
可以理解成
function test1(){
console.log(1)
new Promise((resolve,reject)=>{
async2()
resolve()
}).then(()=>{
console.log(2)
})
}
test
下面来试试
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
-
注册宏任务(打印6)
-
执行
async1函数 -
打印1
-
执行
async2函数 -
打印3
-
执行
async2里Promise的同步代码,打印4 -
这里既是
async又返回Promise -
async1的await函数后半变成注册微任务的代码,这里由于async2是一个async函数,且返回Promise,所以相当于async function async1 () { console.log('1') async2.then(()=>{ console.log("AAA") }) } function async2() { return new Promise<void>((resolve, reject) => { console.log(3) resolve(new Promise((resolve, reject) => { resolve() console.log(4) })) }) } // 尝试后,输出结果一致 -
执行
Promise里面的同步代码,打印7,并且注册微任务(打印8) -
打印11,宏任务结束
-
剩下的就是微任务队列中的两个任务分别执行后又注册新的微任务,只不过
async2注册的微任务一直没有输出,直到最后一个微任务打印"AAA" -
所以打印 8 9 AAA 10
-
所有微任务执行结束,执行宏任务打印6
补充
如果想让"AAA"在10后面打印,也可以再加一层
async function async1 () {
console.log('1')
await async2()
console.log('AAA')
}
async function async2 () {
console.log('3')
return new Promise((resolve, reject) => {
resolve(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 10 AAA 6
总结
平时没事别瞎给async函数返回带then方法的对象,其他都还可以理解(微任务排队罢了)
所以原帖子说的也对,可看作是已经提炼出了简单直接的做法(毕竟一个async,一个Promise,最多出2层)
以上只是我粗浅的理解,可能会有偏差.(叠甲) (平时感觉能用到这知识的机会也不多..)