网站
async function - JavaScript | MDN
引入
Promise 用链式调用解决了回调地狱,但 .then() 写多了代码还是会比较长。
ES2017 引入了 async/await,它本质上是 Promise 的语法糖,但能让异步代码看起来像同步代码一样直观。
正文
定义
定义
async用来声明一个异步函数,这个函数总是返回一个 Promise。await用在async函数内部,用来等待一个 Promise 的结果。
async
async 放在 function 前面,表示这是一个异步函数。
async function sayHello() {
return "你好";
}
sayHello().then(function (result) {
console.log(result);
});输出:你好
async 函数会自动把返回值包装成 Promise。
上面这段代码等价于:
function sayHello() {
return Promise.resolve("你好");
}async 的不同写法
函数声明:
async function getData() {
return "数据";
}函数表达式:
const getData = async function () {
return "数据";
};箭头函数:
const getData = async () => {
return "数据";
};对象方法:
const obj = {
async getData() {
return "数据";
}
};await
await 用来等待一个 Promise 的结果。
await 只能在 async 函数内部使用。
async function loadData() {
const result = await fetch("/api/data");
const data = await result.json();
console.log(data);
}执行流程:
await fetch(…)→ 等待请求完成- 拿到
response对象 await result.json()→ 等待解析完成- 拿到
data
await 的本质
await 就是 .then() 的简写。
Promise 写法:
function loadData() {
return fetch("/api/data")
.then(function (response) {
return response.json();
})
.then(function (data) {
console.log(data);
});
}async/await 写法:
async function loadData() {
const response = await fetch("/api/data");
const data = await response.json();
console.log(data);
}两种写法结果完全一样,但 async/await 写起来更像同步代码。
对比 Promise 链和 async/await
Promise 链:
function getUserOrders() {
return getUser()
.then(function (user) {
return getOrders(user);
})
.then(function (orders) {
return getOrderDetail(orders[0]);
})
.then(function (detail) {
console.log(detail);
})
.catch(function (error) {
console.log(error.message);
});
}async/await:
async function getUserOrders() {
try {
const user = await getUser();
const orders = await getOrders(user);
const detail = await getOrderDetail(orders[0]);
console.log(detail);
} catch (error) {
console.log(error.message);
}
}async/await 的优势:
- 代码从上往下读,逻辑清楚
- 每一步的结果保存在变量里,方便使用
- 不需要
.then()嵌套
错误处理:try/catch
async/await 中用 try/catch 来处理错误。
async function loadData() {
try {
const response = await fetch("/api/data");
const data = await response.json();
console.log(data);
} catch (error) {
console.log("加载失败:" + error.message);
}
}如果 await 的 Promise 被 reject,错误会被 catch 捕获。
也可以配合 finally 使用:
async function loadData() {
try {
const response = await fetch("/api/data");
const data = await response.json();
console.log(data);
} catch (error) {
console.log("加载失败");
} finally {
console.log("请求结束");
}
}多个 await 的错误分别处理
async function loadData() {
let user = null;
let orders = null;
try {
user = await getUser();
} catch (error) {
console.log("获取用户失败");
return;
}
try {
orders = await getOrders(user);
} catch (error) {
console.log("获取订单失败");
}
}每个 await 用单独的 try/catch,可以针对不同错误做不同处理。
顺序执行和并行执行
顺序执行(一个接一个)
async function loadAll() {
const data1 = await fetchData("/api/a");
const data2 = await fetchData("/api/b");
const data3 = await fetchData("/api/c");
console.log(data1, data2, data3);
}三个请求依次执行,每个等前一个完成才开始。总耗时 = 三个请求时间之和。
并行执行(同时进行)
async function loadAll() {
const results = await Promise.all([
fetchData("/api/a"),
fetchData("/api/b"),
fetchData("/api/c")
]);
console.log(results[0], results[1], results[2]);
}三个请求同时发出,等全部完成后拿到结果。总耗时 = 最慢的那个请求时间。
并行执行的另一种写法
async function loadAll() {
const p1 = fetchData("/api/a");
const p2 = fetchData("/api/b");
const p3 = fetchData("/api/c");
const data1 = await p1;
const data2 = await p2;
const data3 = await p3;
console.log(data1, data2, data3);
}先同时发起请求,再分别 await。效果和 Promise.all() 类似。
对比顺序和并行
| 方式 | 写法 | 耗时 |
|---|---|---|
| 顺序执行 | 依次 await | 所有请求时间之和 |
| 并行执行 | Promise.all() | 最慢的请求时间 |
如果请求之间没有依赖关系,应该用并行执行。
条件使用 await
async function loadData(useCache) {
let data;
if (useCache) {
data = getFromCache();
} else {
data = await fetchData("/api/data");
}
console.log(data);
}await 可以放在 if 语句中,只在需要时等待。
在循环中使用 await
async function loadUsers(ids) {
const users = [];
for (const id of ids) {
const user = await getUser(id);
users.push(user);
}
console.log(users);
}注意 for…of 可以配合 await,但 forEach 不行。
错误写法:
async function loadUsers(ids) {
ids.forEach(async function (id) {
const user = await getUser(id);
console.log(user);
});
}forEach 不会等待 await,所有请求会同时发出,结果不好控制。
常见写法
| 写法 | 作用 |
|---|---|
async function fn() {} | 声明异步函数 |
await promise | 等待 Promise 结果 |
try { await fn() } | 成功处理 |
catch (error) {} | 失败处理 |
Promise.all([…]) | 并行执行多个 Promise |
特点
async函数总是返回 Promiseawait暂停执行,等 Promise 完成后继续await只能在async函数内部使用- 代码从上往下读,像同步代码一样清楚
try/catch统一处理错误- 可以和
Promise.all()配合实现并行请求
注意事项
await不能写在普通函数里,只能写在async函数或顶层模块中await会让函数暂停,但不会阻塞函数外面的代码- 多个没有依赖的请求应该用
Promise.all()并行执行 forEach中不能使用await,要用for…of- 不要忘记
try/catch,否则错误会变成一个未捕获的 Promise rejection
理解
可以把 async/await 理解为”让异步代码排队等结果”的写法。
async 就是告诉 JS:“这个函数里有需要等的东西。”
await 就是告诉 JS:“在这里等一下,拿到结果再往下走。”
跟同步代码的区别是:同步是让整条线程停下来等,await 只是让这个函数暂停,其他代码照常运行。
就像在餐厅等菜:你点了菜(await),服务员去做别的桌的事(其他代码继续运行),你的菜好了再端给你(Promise resolve),你继续吃(函数继续执行)。
try/catch 就是提前想好:“万一菜没做出来怎么办。“
引出
async/await 是目前 JS 异步编程最推荐的写法,它建立在 JS Promise 之上。
实际开发中,async/await 经常和 JS 网络请求 配合使用,比如 fetch() 请求数据。