js本身是单线程的,使用async 关键字的函数会被标记成异步函数,需要注意的是,这个异步与Java中的线程不同,它需要await 来实现阻塞才能实现伪异步,它不是并发去跑async 函数内部的代码逻辑,而是等待结果的过程中去运行主函数(等待结果是指ajax请求已经发出,但是没接收到结果,代码逻辑已经跑完,且在跑async函数内代码逻辑的时候是同步的),主函数运行结束后再处理异步函数的结果
例如java中代码:
public class test {
public static void main(String[] args) {
Thread a= new Thread(new Runnable() {
@Override
public void run() {
Integer i=0;
while (i<10000000) {
i++;
}
System.out.println("a执行完毕");
}
});
Thread b= new Thread(new Runnable() {
@Override
public void run() {
Integer i=0;
while (i<1000) {
i++;
}
System.out.println("b执行完毕");
}
});
a.start();
b.start();
System.out.println("主线程结束");
}
}
//结果--即使是a先执行,但是b先出结果,说明b的执行不会被a影响
主线程结束
b执行完毕
a执行完毕
js中类似代码:
async function B1() {
return new Promise((resolve,reject)=>{
resolve("return的结果");
i=0
while(i<1100000000){
i++
}
console.log("B1--执行");
})
}
async function B2() {
i=0
while(i<100){
i++;
}
console.log("B2--执行");
}
B1();
B2();
console.log("结束")
//输出结果--无论怎么运行,依旧是这个结果
B1--执行
B2--执行
结束
js是单线程的,不能做到java那样a和b不相互影响,单它允许阻塞当前节点,再等待结果的过程中(等待结果是指ajax请求已经发出,但是没接收到结果,代码逻辑已经跑完,且在跑async函数内代码逻辑的时候已经是同步的),去运行主节点,就到了await关键字的作用了
运行线程跳出当前子节点,返回父节点
示例代码:
async function B1() {
i=0
console.log("B1--执行");
B2();
console.log("B2--执行执行完毕");
}
async function B2() {
console.log("B2--执行");
}
B1();
//节点B
console.log("主线程---当前是最后打印的吗?");
// 执行结果:依旧是同步的?依旧是同步的
B1--执行
B2--执行
B2--执行执行完毕
主线程---当前是最后打印的吗?
需要改成:
实现伪异步:
async function B1() {
i=0
console.log("B1--执行");
await B2(); // 等待B2结果,阻塞当前节点A ,如果B2方法内部没有await 那么会跳转到节点B
// 节点A
console.log("B2--执行执行完毕");
}
async function B2() {
console.log("B2--执行");
}
B1();
//节点B
console.log("主线程---当前是最后打印的吗?");
// 结果:
B1--执行
B2--执行
主线程---当前是最后打印的吗?
B2--执行执行完毕
为什么我会说是假异步呢,如果往节点B后面添加给=个循环,那么它将会影响节点A的执行
例如:
async function B1() {
i=0
console.log("B1--执行");
await B2(); // 等待B2结果,阻塞当前节点A
// 节点A
console.log("B2--执行执行完毕");
}
async function B2() {
console.log("B2--执行");
}
B1();
//节点B
console.log("主线程---当前是最后打印的吗?");
i=0
while(i<1000000){ // 当前循环依旧会影响到节点A,当循环结束才会 打印 "B2--执行执行完毕"
i++;
console.log(i);
}
那么我们就能非常确的它代码的执行顺序
例如代码:
async function B1() {
// 节点A
console.log("结束B1")
}
async function B2(){
await B1();
// 节点B
console.log("结束B2")
}
async function B3(){
await B2();
// 节点C
console.log("结束B3")
}
// 运行
B3();
// 节点D
console.log("结束")
/**
* 输出结果:
*
* 结束B1
* 结束
* 结束B2
* 结束B3
*
*/
分析:
说明:await不会马上跳出当前节点,会先判断当前await A()中的A方法中是否还有await,如果有就继续深入,如果没有就跳出到主节点D中。
具体流程:
线程运行到B3()方法,B3方法中有await B2() 会进入B2() 中,而B2中有await B1() ,那么最终会进入进入到B1 ,执行输出 ”结束B1“,之后因为B1内没有了await,那么会跳到主节点节点D中,节点D中执行 ”结束“,之后返回到节点B中执行 ”结束B2“ ,之后跳到节点C中 执行”结束B3“,至此程序结束。
那么对比下面,我们也能猜到下面程序的执行顺序
var a1=0;
async function A1(){
a1++;//3 5
console.log("A1",a1); // 执行顺序1 // 执行顺序4
}
async function A2(){
a1++;//2 4
await A1();
console.log("A2",a1); // 执行顺序3 // 执行顺序5
}
async function A4(){
a1++;//1
await A2();
await A2();
console.log("A4",a1); // 执行顺序6
}
A4()
console.log("主节点",a1); // 执行顺序2
/**
执行结果:
A1 3
主节点 3
A2 3
A1 5
A2 5
A4 5
**/
只需要记得嵌套最里面层的await 等待异步函数的全部执行,执行完毕后才会才会跳到主节点,也就是所js的异步方法执行顺序的可控的,它的异步不是java的线程并发,而是“后续再处理执行结果”,等待主节点完成后再执行await下面的内容,异步函数的顺序也是按事件队列的顺序执行,并不是并发。如果不使用await那么异步函数的执行顺序和没有使用async关键字是一样的,即就是一个普通函数。
async 标记的函数的返回值都是Promise 类型的
即使没有直接标明是Promise 它依旧会被封装成Promise
例如:
async function C1(){
i=0;
while(i<1000000000){
i++
}
console.log("C1");
return "结果1"
}
async function C2(){
i=0;
while(i<100000){
i++
}
console.log("C2");
return "结果2"
}
function A(){
C1().then((data)=>{
i=0;
while(i<1000000000){ // then的运行不会影响主节点,他是需要在主节点结束后在运行
i++
}
console.log("C1运行结果:"+data);
i=0;
})
console.log("C1执行完毕当前依旧会被阻塞");
C2().then((data)=>{ // then的运行不会影响主节点,他是需要在主节点结束后在运行,但then的运行也不是并发,也是按事件顺序
i=0;
while(i<1000){ // 当前处理短,但是也不会优先于C1
i++
}
console.log("C2结果:"+data);
})
console.log("C2执行完毕当前节点会被阻塞C1,C2的逻辑阻塞,但是不会被then阻塞");
}
console.log("主节点不会被阻塞1");
A()
console.log("主节点还是会被阻塞C1,C2的逻辑阻塞,但是不会被then阻塞2");
执行结果是唯一的:
主节点不会被阻塞1
C1
C1执行完毕当前依旧会被阻塞
C2
C2执行完毕当前节点会被阻塞C1,C2的逻辑阻塞,但是不会被then阻塞
主节点还是会被阻塞C1,C2的逻辑阻塞,但是不会被then阻塞2
C1运行结果:结果1
C2结果:结果2
它们本质上
下面举例说明:
console.log("Start");
async function fetchData() {
try {
console.log("fetchDat");// 当前还是立刻执行的,还是比console.log("End"); 先执行
const response = await axios.get('https://api.example.com/data'); // 立即发送请求
console.log(response.data); //response 有结果才会执行,即最后执行
} catch (error) {
console.error(error);
}
}
fetchData();
console.log("End");
对比then
console.log("Start");
async function fetchData() {
console.log("fetchData"); // 当前是立刻执行的 console.log("Start"); 结束后执行
axios.get('https://api.example.com/data') // 当前是立刻执行的 console.log("fetchData");结束后执行
.then((data)=>{ // then或者catch里面的内容的是在 console.log("End");结束后,即使主节点结束后再执行的
console.log("最后执行");
})
.catch((err)=>{
console.log("最后执行");
})
console.log("fetchData--edd"); //在 axios.get('https://api.example.com/data') 发送请求结束后执行
}
fetchData();
console.log("End");
then 的内部代码块就相当于await 的下面部分的代码块,本质上没什么区别。
async function ad1(){
try{
let a= await ad2()
console.log("数据1:",a);
}catch(e){
console.log("错误:"+e);
}
}
async function ad2(){
return new Promise((resolve,reject)=>{
// let a=c/0
console.log("输出");
let i=0
while(i<10000){
i++;
}
console.log("数据2");
resolve(1/1) // 正确结果
// reject(new Error("错误"))
})
}
ad1();
console.log("在--数据2--后面");
其实return Promise和直接return正确结果是一致的,本质上async 会对返回值进行封装。
总之:js的异步函数和普通函数一样会执行,只是执行的结果会在主函数结束后再处理,异步函数并没有并发,依旧是按顺序执行。