await 与async 基本概念
这是Js异步的一种实现方式,一传统的promise不同,await让代码的书写更像我们传统的同步代码的方式,也就是这样:
let result = await fun();
而不像我们之前使用promise,需要调用then方法传入回调使用
async :
用在函数的开头,代表这个函数是一个异步函数
- 其return的任何值都会被包装成promise
- 在async声明的函数中可以使用
await
调用其他异步函数
await:
- 只能在async声明的异步函数中或者模块的外层作用域使用
- 使用await调用异步函数,相当于调用promise的then方法
- 只能通过try-catch 来处理异常
看代码
console.log("script start")
new Promise((resolve) => {
console.log("promise1")
resolve('微任务1')
})
.then((data) => { //第一个Promise加入微任务列表
console.log(data)
})
async function fun1() {
console.log("fun1")
let data = await fun2() //关键的地方,await后面的代码会阻塞 ,fun2执行完毕后,后面的代码类似于传入then()中的回调
console.log(data)
console.log("await堵塞")
}
setTimeout(() => {
console.log("setTimeout 宏任务") //一个宏任务,加入宏任务队列
}, 0)
async function fun2() {
console.log("fun2")
// return "no promise"
return new Promise((resolve, reject) => {
resolve("fun2 微任务")
})
// return "await"
}
fun1() //代码开始执行
new Promise((resolve) => {
console.log("promise2")
resolve("微任务2")
})
.then((data) => { //promise状态已经fulfilled,直接加入微任务队列
console.log(data)
})
console.log("同步end") // 同步代码结束
上面代码,执行结果
script start
promise1
fun1
fun2
promise2
同步end
微任务1
微任务2
fun2 微任务
await堵塞
setTimeout 宏任务
这种情况是: await后面的异步函数return的是一个Promise
再看下面这种情况:
console.log("script start")
new Promise((resolve) => {
console.log("promise1")
resolve('微任务1')
})
.then((data) => { //第一个Promise加入微任务列表
console.log(data)
})
async function fun1() {
console.log("fun1")
let data = await fun2() //关键的地方,await后面的代码会阻塞 ,fun2执行完毕后,后面的代码类似于传入then()中的回调
console.log(data)
console.log("await堵塞")
}
setTimeout(() => {
console.log("setTimeout 宏任务") //一个宏任务,加入宏任务队列
}, 0)
async function fun2() {
console.log("fun2")
return "no promise"
// return new Promise((resolve, reject) => {
// resolve("fun2 微任务")
// })
// return "await"
}
fun1() //代码开始执行
new Promise((resolve) => {
console.log("promise2")
resolve("微任务2")
})
.then((data) => { //promise状态已经fulfilled,直接加入微任务队列
console.log(data)
})
console.log("同步end") // 同步代码结束
这种情况是,await后面的异步函数return的不是Promise
执行结果:
script start
promise1
fun1
fun2
promise2
同步end
微任务1
no promise
await堵塞
微任务2
setTimeout 宏任务
从结果上来看,await后面的异步函数return的是否是Promise,执行顺序的不同点在于await后面的代码执行是在微任务2之前还是之后执行,也就是微任务队列顺序的问题
当return的是Promise时:await后面的代码会在微任务2之后执行
当return的不是Promise时:await后面的代码会在微任务2之前执行
分析原因:
当return的不是promise时
这个也是我们通常的用法,因为async声明的异步函数会自动将返回包装成promise ,到调用fun2这个异步函数时,await就可以看做是使用then方法,会将回调函数(这里就是await后面的代码) 放入微任务队列,这里是排在微任务一之后,微任务二之前,所以这里await后面的代码会在微任务2之前执行
当return的是Promise时
这个用法会奇怪,但是面试题是这样写的😓
此时通过调试发现
在异步函数中return一个promise 返回的结果是一个 pending 状态的promise ,value为undefined
显然这个promise不是我们返回的promise,我们return的promise的状态肯定fulfilled,值为“no promise” 。但是此时我们data接收到的值仍然为“no promise”,通过浏览器调试,可以发现,如果在调试时没有去查看(打开)这个promise,最后代码全部执行完毕再去打开,会发现
这个promise状态又变正常了
说明在打印之后这个promise的状态发生了改变,现在问题就变成了这个promise到底是什么时候状态变成了fulfilled
说说async的实现原理,都说async是Generator的语法糖,那到底他们有什么关系呢?
async function fn(args) {
// ...
}
// 等同于
function fn(args) {
return spawn(function* () {
// ...
});
}
spawn函数是自动执行器,说简单一点,async会自动的调用next,并把return结果放在一个promise的resolve中
function spawn(genF) {
return new Promise(function(resolve, reject) {
const gen = genF();
function step(nextF) {
let next;
try {
next = nextF();
} catch(e) {
return reject(e);
}
if(next.done) {
return resolve(next.value);
}
Promise.resolve(next.value).then(function(v) {
step(function() { return gen.next(v); });
}, function(e) {
step(function() { return gen.throw(e); });
});
}
step(function() { return gen.next(undefined); });
});
}
改一下之前fun2的代码
function fun2() {
console.log('fun2');
// return 'fun2 微任务';
return new Promise((resolve, reject) => {
resolve(new Promise((res) => {
res('fun2 微任务');
}));
});
// return new Promise((resolve, reject) => {
// resolve('fun2 微任务');
// });
// return "await"
}
没有用async模拟一下
结果与之前一样,执行顺序也一样
再次调试发现,当执行到微任务2结束之后下一个宏任务之前,此时fun2返回的promise状态会变为fulfilled
重点!!!
当resolve的参数是一个promise时,此时会将这个参数promise放入微任务队列(此时排在微任务1之后),而他的父级promise状态不会改变,等到微任务队列中执行到这个promise时,父promise的then方法绑定到子promise上,所以这就解释了上面的执行顺序的问题
附上最终版的代码
console.log('script start');
let fun2Data;
new Promise((resolve) => {
console.log('promise1');
resolve('微任务1');
}).then((data) => {
//第一个Promise加入微任务列表
console.log(data);
});
async function fun1() {
console.log('fun1');
fun2Data = fun2(); //关键的地方,await后面的代码会阻塞 ,fun2执行完毕后,后面的代码类似于传入then()中的回调
console.log(fun2Data);
let data = await fun2Data;
console.log(data);
console.log('await堵塞');
}
setTimeout(() => {
console.log('setTimeout 宏任务'); //一个宏任务,加入宏任务队列
}, 0);
function fun2() {
console.log('fun2');
// return 'fun2 微任务';
let innerPromise = new Promise((res) => {
res('fun2 微任务');
}).then((res) => {
console.log("内部promise",res);
return "内部promise返回"
});
let outPromise = new Promise((resolve, reject) => {
resolve(innerPromise);
});
return outPromise
// return new Promise((resolve, reject) => {
// setTimeout(() => {
// resolve('fun2 微任务');
// }, 5000);
// });
// return "await"
}
fun1(); //代码开始执行
new Promise((resolve) => {
console.log('promise2');
resolve('微任务2');
}).then((data) => {
//promise状态已经fulfilled,直接加入微任务队列
console.log(data);
});
console.log('同步end'); // 同步代码结束
/**
* await 在执行到此微任务时才会将值返回
* await后面不管是什么内容,都会加入微任务队列
* 如果fun2不是async,返回是字符串和返回promise执行顺序一致,都按照微任务列表执行
* 如果fun2为async,返回字符串同上,如果返回promise,此时任务会被放在微任务末尾,最后执行
*/