javaScript真的反人类吗?

本文探讨了JavaScript中的异步执行、async/await和Promise的工作原理,强调了JavaScript的事件循环和微任务队列的概念。通过多个代码示例,解释了await如何影响代码执行顺序,以及如何正确使用异步操作实现特定的执行流程。
摘要由CSDN通过智能技术生成

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;
如这里,f3await是在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,则出队后立即入队,然后等下一次轮询集体出队,然后再入队,先进先出。

入队不等于执行,出队才是执行。入队是准备执行。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值