javaScript真的反人类吗?
公司来了个新人,问了我这么一个问题。如下:
// 代码段1
let a=async ()=>{
for(let i=0;i<100;I==){
console.log("a")
}
}
let b=()=>{
a()
console.log("b")
}
// 代码段2
let a=async ()=>{
for(let i=0;i<100;I==){
console.log("a")
}
}
let b=()=>{
await a()
console.log("b")
}
她说:为啥我的代码段1和代码段2执行的结果一样?
我一看,嘿好家伙,这不一样,那你还想它咋执行。
她说:我觉得代码段一,没用await 而且调用的还是async异步函数,应该是b先执行再执行a啊。
好家伙,你当Go开线程呢一个async就开一个线程?
于是以下面代码为例
// 代码段1
let as= ()=>{
for(let i=0;i<100;i++){
(async ()=>{})().then(async ()=>{
console.log("p")
})
console.log("1")
}
console.log("2")
}
as()
// 代码段2
let as=async ()=>{
for(let i=0;i<100;i++){
(async ()=>{})().then(async ()=>{
console.log("p")
})
console.log("1")
}
console.log("2")
}
as()
// 代码段3
let as= ()=>{
for(let i=0;i<100;i++){
new Promise((a,b)=>{a()}).then(async ()=>{
console.log("p")
})
console.log("1")
}
console.log("2")
}
as()
// 代码段4
let as= async()=>{
for(let i=0;i<100;i++){
new Promise((a,b)=>{a()}).then(async ()=>{
console.log("p")
})
console.log("1")
}
console.log("2")
}
as()
// 代码段 5
let as= async()=>{
for(let i=0;i<100;i++){
setTimeout(()=>{console.log("set")},0)
console.log("1")
}
console.log("2")
}
as()
// 代码段 6
let as= ()=>{
for(let i=0;i<100;i++){
setTimeout(()=>{console.log("set")},0)
console.log("1")
}
console.log("2")
}
as()
以上代码可知,
1.Js本身就是异步调用。
2.async
不准确的来说,有时候就只是为了在函数内用await
才写async
。所以,并不async
开启了异步,而是Js本身就是异步。
3.async
其实就是Promise
的构造函数的手动调用版本。比如上面代码的那个
(async()=>{})() === new Promise((reslove,reject)=>{})
又比如下面的代码:
// 代码段A
let b=async()=>{
setTimeout(()=>{console.log("set")},0)
}
let as=async ()=>{
for(let i=0;i<100;i++){
await b();
console.log("1")
}
console.log("2")
}
as()
你会发现,await
失效了?
并不是失效了,而是这里的await
只是等待了b
的执行而没有等待到setTimeout的回调消息队列
。
故而会是1,2,set
的顺序。
那么下面这段代码就有意思的多
// 代码段A
let b=async()=>{
new Promise((reslove,reject)=>{reslove()}).then(async ()=>{
console.log("pt")
})
}
let as=async ()=>{
for(let i=0;i<100;i++){
b();
console.log("1")
}
console.log("2")
}
as()
// 代码段B
let b=async()=>{
new Promise((reslove,reject)=>{
reslove()
}).then(async ()=>{
console.log("pt1")
}).then(async ()=>{
console.log("pt2")})
new Promise((reslove,reject)=>{reslove()}).then(async ()=>{
console.log("newpt")
})
new Promise((reslove,reject)=>{reslove()}).then(async ()=>{
console.log("newpt2")
})
new Promise((reslove,reject)=>{reslove()}).then(async ()=>{
console.log("newpt3")
})
}
let as=async ()=>{
await b();
console.log("2")
}
as()
// 代码段C
let b=async()=>{
new Promise((reslove,reject)=>{
reslove()
}).then(async ()=>{
console.log("pt1")
}).then(async ()=>{
console.log("pt2")})
new Promise((reslove,reject)=>{reslove()}).then(async ()=>{
console.log("newpt")
})
new Promise((reslove,reject)=>{reslove()}).then(async ()=>{
console.log("newpt2")
})
new Promise((reslove,reject)=>{reslove()}).then(async ()=>{
console.log("newpt3")
})
}
let as=async ()=>{
await b();
console.log("2")
}
as()
这个执行顺序就不一样了。
代码段B:
首先 as
被压入调用栈 进入 as
体内 然后 b
被压入调用栈 执行构造函数
并将各个构造函数的.then
入队到微任务队列,因为有await
所以 各个构造函数的.then
出队(输出),然后执行console.log(“2”);
代码段C:
首先 as
被压入调用栈 进入 as
体内 然后 b
被压入调用栈 执行构造函数
并将各个构造函数的.then
入队到微任务队列,因为没有await
所以 各个构造函数的.then
会在调用栈清空后执行。然后当第一个.then
执行完毕后入队第二个.then
,调用栈清空后再次执行。
注意:await的位置,await只是等待,放在函数内,就在只是在那个函数内等待,并且await只等待一层,如下面的代码:
let p1 = new Promise((reslove, reject) => {
reslove()
console.log(1)
})
.then(res => {
console.log(2)
})
.then(res => {
console.log(3)
})
.then(res => {
console.log(4)
})
let p2 = new Promise((reslove, reject) => {
console.log(5)
reslove()
})
.then(res => {
console.log(6)
})
.then(res => {
console.log(7)
})
let f3 = async () => {
await new Promise((reslove, reject) => {
console.log(8)
reslove()
})
.then(res => {
console.log(9)
})
.then(res => {
console.log(10)
})
.then(res => {
console.log(11)
})
new Promise((reslove, reject) => {
console.log(12)
reslove()
})
.then(res => {
console.log(13)
})
.then(res => {
console.log(14)
})
}
let f4 = async () => {
new Promise((reslove, reject) => {
console.log(15)
reslove()
})
.then(res => {
console.log(16)
})
.then(res => {
console.log(17)
}).then(res=>{
console.log(18)
})
}
(async () => {
f3()
await f4()
console.log(123)
})()
这里的执行顺序是1,5,8,15,2,6,9,16,123,3,7,10,17,4,11,18,12,13,14;
因为兄弟关系的await
会阻塞整个后面的程序。而父级的await
只会阻塞一层。*”且父级的await会在子级的await执行完毕后再执行。“ *这句话可能不太准确,但是如下代码可以看出:
let p1 = new Promise((reslove, reject) => {
reslove()
console.log(1)
})
.then(res => {
console.log(2)
})
.then(res => {
console.log(3)
})
.then(res => {
console.log(4)
})
let p2 = new Promise((reslove, reject) => {
console.log(5)
reslove()
})
.then(res => {
console.log(6)
})
.then(res => {
console.log(7)
})
let f3 = async () => {
await new Promise((reslove, reject) => {
console.log(8)
reslove()
})
.then(res => {
console.log(9)
})
.then(res => {
console.log(10)
})
.then(res => {
console.log(11)
})
new Promise((reslove, reject) => {
console.log(12)
reslove()
})
.then(res => {
console.log(13)
})
.then(res => {
console.log(14)
})
}
let f4 = async () => {
new Promise((reslove, reject) => {
console.log(15)
reslove()
})
.then(res => {
console.log(16)
})
.then(res => {
console.log(17)
}).then(res=>{
console.log(18)
})
}
(async () => {
await f3()
f4()
console.log(123)
})()
这里的执行顺序是:1,5,8,2,6,9,3,7,10,4,11,12,13,15,123,14,16,17,18;
如这里,f3
的await
是在f3
内部的await
执行完毕后,且消息队列清空后,执行的12,按常理说执行完12
,应该调用主进程的f4
可是因为f3
外部的await
故而会再次等待一层执行完毕13,然后在调用主进程的f4
;
当微任务队列清空后才会去调用主进程,并且调用的优先级是由内而外的。比如先调用了f3的第二个new并且因为外部的await
等待了.then
再调用的是f4。
总结:
1.js本身就是异步执行与async无关。
2.async函数体内如同Promise的构造函数体内,并不是开启了异步。
3.await等待的是微任务,如.then
4.for循环阻塞与否在于for体内执行的任务。
5.想要异步得是微任务或者setTimeout之类的回调。
6.await阻塞兄弟会阻塞死,阻塞子级只阻塞一级。
7.消息队列清空后才会调用主进程,且优先级是由内而外的。
**8.await后面的会等微任务队列清空了才继续执行下面的任务**
**(这里的清空指的是在“我”之前的入队,** **比如4是在11前入队的微任务,故而必须把11之前入队的微任务执行完毕。await才会允许下面的new执行。**
**再比如,要执行f4必须要把13执行完毕才能执行。因为在new时13已经入队,然后就该执行f4,但是有个await则需要把已经入队的微任务执行完毕再执行f4。故而15会在13后输出。
解决下最开始公司新人的问题,她想要让for在`b`输出执行之后执行,故而有两种选择如下
// 1
let as= async()=>{
for(let i=0;i<100;i++){
setTimeout(()=>{console.log("for")},0)
}
console.log("b")
}
as()
// 2
let as= async()=>{
for(let i=0;i<100;i++){
new Promise((reslove,reject)=>{reslove()}).then(async ()=>{
console.log("for")
})
}
console.log("b")
}
as();
最后记住:
await的阻塞是"兄弟"层面的阻塞,await下面的任务要执行必须等在该任务前已经入队的微任务执行完毕后再执行。
简单的例子:
(async () => {
await f3()
f4()
console.log(123)
})()
f4要执行,必须等f3中已经入队的微任务执行完毕后才能执行f4。
再如:
let f3 = async () => {
await new Promise((reslove, reject) => {
console.log(8)
reslove()
})
.then(res => {
console.log(9)
})
.then(res => {
console.log(10)
})
.then(res => {
console.log(11)
})
new Promise((reslove, reject) => {
console.log(12)
reslove()
})
.then(res => {
console.log(13)
})
.then(res => {
console.log(14)
})
}
let f4 = async () => {
new Promise((reslove, reject) => {
console.log(15)
reslove()
})
.then(res => {
console.log(16)
})
.then(res => {
console.log(17)
}).then(res=>{
console.log(18)
})
}
(async () => {
await f3()
f4()
console.log(123)
})()
函数进入到f3中,执行到await则把兄弟间阻塞死了
,只有等
await new Promise((reslove, reject) => {
console.log(8)
reslove()
})
.then(res => {
console.log(9)
})
.then(res => {
console.log(10)
})
.then(res => {
console.log(11)
})
后,执行到
new Promise((reslove, reject) => {
console.log(12)
reslove()
})
时,.then(res => { console.log(13) })
已经入队,又因为await f3()
故而需要把在f4
之前入队的微任务执行完毕才执行f4
。
主任务大于微任务大于消息队列
但是在主任务前入队的微任务用于await的必须在主任务前执行。
如果链式调用.then,则出队后立即入队,然后等下一次轮询集体出队,然后再入队,先进先出。
入队不等于执行,出队才是执行。入队是准备执行。