Javascript 笔记:Async 函数
这篇文章记录 Async 函数的基本概念和使用方法。
概念
使用async关键字声明的函数,称之为异步函数。在普通函数前面加上 async 关键字,就成了异步函数。语法举例:
1 | // 写法1:函数声明的写法 |
JS 中的“异步函数”是一个专有名词,特指用 async
关键字声明的函数,其他函数则称之为普通函数。如果你在一个普通函数中定义了一个异步任务,那并不叫异步函数,而是叫包含异步任务的普通函数。
上面的异步函数代码,执行顺序与普通函数相同,默认情况下会同步执行。如果想要发挥异步执行的作用,则需要配合
await
关键字使用。
异步函数的返回值
普通函数的返回值,默认是 undefined;也可以手动 return 一个返回值,那就以手动 return 的值为准。
异步函数的返回值永远是 Promise 对象。至于这个 Promise 后续会进入什么状态,那就要看情况了。主要有以下几种情况:
- 情况1:如果异步函数内部返回的是
普通值(包括 return undefined 时)或者普通对象
,那么Promise 的状态为 fulfilled。这个值会作为 then() 回调的参数。 - 情况2:如果异步函数内部返回的是
另外一个新的 Promise
,那么 Promise 的状态将交给新的 Promise 决定。 - 情况3:如果异步函数内部返回的是一个对象,并且这个对象里有实现then()方法(这种对象称为 thenable 对象),那就会执行该 then()方法,并且根据 then() 方法的结果来决定Promise的状态。
- 情况4:这是一种特殊情况,如果异步函数内部在执行时遇到异常或者手动抛出异常时,那么, Promise 处于rejected 状态。
代码举例: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// 情况1, 返回 undefined
async function foo1() {
// 相当于 return undefined,也相当于 return Promise.resolve(undefined)
};
// 情况1,返回普通对象
async function foo2() {
// 相当于 return Promise.resolve('qianguyihao');
return 'qianguyihao'
};
// 打印结果
foo1().then(res => {
console.log(res); // 打印结果:undefined
})
foo2().then(res => {
console.log(res); // 打印结果:qianguyihao
})
async/await 的使用
们可以在 async
声明的异步函数中,使用 await
关键字来暂停函数的执行,等待一个异步操作完成。温馨提示:await
关键字不能在普通函数中使用,只能在异步函数中使用。
在等待异步操作期间,异步函数会暂停执行,并让出线程,使其他代码可以继续执行。一旦异步操作完成,该异步函数会恢复执行,并返回一个 Promise 对象。具体解释如下:
- await的后面是一个表达式,这个表达式要求是一个 Promise 对象(通常是一个封装了异步任务的Promise对象)。await执行完成后可以得到异步结果。
- await 会等到当前 Promise 的状态变为 fulfilled之后,才继续往下执行异步函数。
- async 的返回值是 Promise 对象。
语法糖
async/await 是在 ES8 (即ES 2017)中引入的新语法,是另外一种异步编程解决方案。
async/await 本质是生成器 Generator 的语法糖,是对 Generator 的封装。什么是语法糖呢?语法糖就是让语法变得更加简洁、更加舒服,有一种甜甜的感觉。
async/await 的写法使得编写异步代码更加直观和易于管理,避免了使用回调函数或 Promise 链的复杂性。
Promise、Generator、async/await的对比
我们在使用 Promise、async/await、Generator 的时候,返回的都是 Promise 的实例。
如果直接使用 Promise,则需要通过 then 来进行链式调用;如果使用 async/await、Generator,写起来更像同步的代码。
下面看下 async/await 的用法。
async/await 的基本用法
async 后面可以跟一个 Promise 实例对象。代码举例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25const request1 = function () {
const promise = new Promise((resolve, reject) => {
requestAjax('https://www.baidu.com/xxx_url', (res) => {
if (res.retCode == 200) {
// 这里的 res 是接口1的返回结果
resolve('request1 success' + res);
} else {
reject('接口请求失败');
}
});
});
return promise;
};
async function requestData() {
// 关键代码
const res = await request1();
return res;
}
requestData().then(res => {
console.log(res);
});
用 async/await 封装 Promise 链式调用
假设现在有三个网络请求,请求2必须依赖请求1的结果,请求3必须依赖请求2的结果,如果按照ES5的写法,会有三层回调,会陷入“回调地狱”。
使用 async/await ,可以把原本的“多层嵌套调用”改成类似于同步的写法,非常优雅。
1 | // 【公共方法层】封装 ajax 请求的伪代码。传入请求地址、请求参数,以及回调函数 success 和 fail。 |
await 后面也可以跟一个异步函数
前面讲到,await
后面通常是一个执行异步任务的 Promise
对象。由于异步函数的返回值本身就是一个
Promise
,所以,我们也可以在 await
后面也可以跟一个异步函数。
代码举例:
1 | const request1 = function () { |
异步函数的异常处理
如果异步函数内部在执行时遇到异常或者手动抛出异常时,那么, 这个异步函数返回的Promise 处于rejected 状态。
捕获并处理异步函数的异常时,有两种方式:
- 方式1:通过 Promise的catch()方法捕获异常。
- 方式2:通过 try catch捕获异常。
在处理异步函数的异常情况时,方式2更为常见。
如果我们不捕获异常,则会往上层层传递,最终传递给浏览器,浏览器会在控制台报错。
方式1:过 Promise的catch()方法捕获异常
代码举例: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31function requestData1() {
return new Promise((resolve, reject) => {
reject('任务1失败');
})
}
function requestData2() {
return new Promise((resolve, reject) => {
resolve('任务2成功');
})
}
async function getData() {
// requestData1 在执行时,遇到异常
await requestData1();
/*
由于上面的代码在执行是遇到异常,所以,这里虽然什么都没写,底层默认写了如下代码:
return Promise.reject('任务1失败');
*/
// 下面这行代码不会再走了
await requestData2();
}
// getData() 这个异步函数的返回值是一个 Promise,状态为 rejected,所以会走到 catch()
getData().then(res => {
console.log(res);
}).catch(err => {
console.log('err:', err);
});
打印结果:
1 | err: 任务1失败 |
方式2:通过 try catch 捕获异常
如果你觉得上面的写法比较麻烦,还可以通过 try catch 捕获异常。
代码举例: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33function requestData1() {
return new Promise((resolve, reject) => {
reject('任务1失败');
})
}
function requestData2() {
return new Promise((resolve, reject) => {
resolve('任务2成功');
})
}
async function getData() {
try {
// requestData1 在执行时,遇到异常
await requestData1();
/*
由于上面的代码在执行是遇到异常,当前任务立即终止,所以,这里虽然什么都没写,底层默认写了如下代码:
return Promise.reject('任务1失败');
*/
// 下面这两代码不会再走了
console.log('qianguyihao1');
await requestData2();
}
catch (err) {
// 捕获异常代码
console.log('err:', err);
}
}
getData();
console.log('qianguyihao2');
打印结果: 1
2qianguyihao2
err1: 任务1失败
总结
在 async 函数中,不是所有的 异步任务都需要 await。如果两个任务在业务上没有依赖关系,则不需要 await;也就是说,可以并发执行,不需要线性执行,避免无用的等待。
参考: