9. async函数
async函数是ES2017引入的标准,该函数可以看作是Generator 函数的语法糖,它对Generator 函数进行了改进
基本用法
大概语法如下:
const asyncFn=async function () {
let res1=await fetch("/api/isLogin");
let res2=await fetch("/api/listPositions");
console.log(res1.json());
console.log(res2.json());
}
以上代码用来顺序执行判断用户是否登陆、获取城市列表信息
语法
asycn函数返回一个Promise对象,和普通函数一样,只需要执行函数,就会自动执行
async
表示函数里有异步操作
async function asyncTest() {}
console.log(asyncTest()) //Promise
在上面的代码中,async后的函数会被转换为一个Promise对象,可以使用then方法添加回调函数
函数内部返回的值会被then方法回调函数接收到,如果在async函数内部抛出错误,会导致返回的Promise对象变为reject状态,抛出的错误对象会被catch函数接收到
async function asyncTest() {
throw new Error("error")
}
asyncTest()
.then(
() => {
console.log("执行");
},
(err)=>{
console.log(err); //error
}
)
await
表示紧跟在其后面的表达式需要等待结果,当函数运行时一旦遇到await是就会先暂停执行下面的操作,在等到异步操作完成时再继续执行接下来的函数体内的语句,await命令后可以是Promise对象或数值、字符串和布尔值这些原始值类型,如果是原始值,那么会被立即转换成一个状态为resolve的Promise对象
async function f() {
return await 123;
}
f().then(v => console.log(v))//123
上面代码中,await命令的参数是数值123,它被转成 Promise 对象,并立即resolve。
例如我们举一个例子,设定一个计时器,在计时器内执行完成之后再执行输出值的操作
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(()=>{
console.log("timeout函数正在执行");
resolve();
}, ms)
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint("timeout函数执行完成", 100);
//timeout函数正在执行
//timeout函数执行完成
由于async函数返回的是Promise对象,可以作为await命令的参数,所以上面的例子也可以写成下面的形式:
async function timeout(ms) {
await new Promise((resolve)=>{
setTimeout(()=>{
console.log("timeout函数正在执行");
resolve();
},ms)
})
}
async function asyncPrint(value,ms) {
await timeout(ms);
console.log(value);
}
asyncPrint('timeout函数执行完成',100);
//timeout函数正在执行
//timeout函数执行完成
async函数返回的是Promise对象,必须等到内部所有await命令后面的Promise对象执行完才会发生状态改变,除非遇到return语句或抛出错误,也就是说只有async函数内部的异步操作执行完才会执行then方法指定的回调函数
async function get() {
let response = await fetch("/api/isLogin");
let html = await response.json();
return html.status;
}
get().then(console.log); //success
上面代码中,函数get内部有两个操作,请求数据,转化数据格式,只有在这两个操作完成后,执行return语句后才会执行then方法中的操作
await命令后面的Promise对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到,只要一个awite语句后面的Promise变为reject,那么整个async函数都会中断执行
async function fn() {
await Promise.reject("error"); //这里不需要加return,加上的效果是一样的
await Promise.resolve("success"); //不会执行
}
fn()
.then(
res => {
console.log(res);
},
res => {
console.log(res);
}
)
//error
上面的代码中第二个await语句不会执行,因为第一个await语句状态变为了reject
有时我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以降低一个await放在try...catch结构中,这样不管这个异步操作是否成共,第二个await都会执行
async function fn() {
try{
await Promise.reject("error");
}catch(e){
console.log(e) ////error
}
return await Promise.resolve("success");
}
fn()
.then(
res => {
console.log(res);
},
res => {
console.log(res);
}
)
//success
或者在第一个Promise后再跟一个catch方法,处理前面可能出现的错误
async function fn() {
await Promise.reject("error")
.catch(res=>{
console.log(res); //error
})
return await Promise.resolve("success");
}
fn()
.then(
res => {
console.log(res);
},
res => {
console.log(res);
}
)
//success
错误处理
再上面的代码中我们已经提到过,如果await后面的异步操作出错,那么等同于async函数返回的Promise对象的状态被北纬reject,导致catch方法被调用,后续的异步函数都不会被执行,防止出错的方法,就是将其放置再try...catch代码块中
如果有多个await命令,我们可以统一将其放在try...catch结构中
async function main() {
try {
const val1 = await firstStep();
const val2 = await secondStep(val1);
const val3 = await thirdStep(val1, val2);
console.log('Final: ', val3);
}
catch (err) {
console.error(err);
}
}
下面的例子,使用try...catch结构,实现多次重复尝试
const NUM_RETERIES=3;
async function fn() {
let i;
for (i=0;i<NUM_RETERIES;i++){
try{
await fetch("/api/isLogin");
break;
}catch(err){
//忽略错误信息
}
}
}
fn();
//代码在try...catch代码块中,如果操作成功,就会使用break语句退出循环;如果失败会被catch语句捕获,然后进入下一轮循环
注意点
-
首先就是我们上面在强调的多个await命令后的Promise对象可能时rejected格式,所以最好吧await命令放在try...catch代码块中
-
如果多个await命令不存在相互依赖的关系,如果写成先后顺序执行的关系会比较消耗时间,例如两个Ajax数据请求,如果一昧的先后执行就相当于同步操作,完全可以让他们同时触发
let [foo,bar]=await Promise.all([isLogin(),cityList()]) //或者 let isLogin=isLogin(); let cityList=cityList(); let login=await isLogin; let lists=await cityList;
例如:
async function timeout() { return await Promise.all([$.get("/api/isLogin"), $.get("/api/cityList")]) } timeout() .then( (e) => { console.log(e); } )