概述
async/await 是 ES2017 引入的异步编程语法,本质上是 Generator 函数的语法糖。它让异步代码的书写方式接近同步代码,大幅提升了可读性。
异步编程的演进
回调地狱
最早的异步处理方式是回调函数,多层嵌套后代码难以维护:
getUser(userId, function (user) {
getOrders(user.id, function (orders) {
getOrderDetail(orders[0].id, function (detail) {
console.log(detail);
// 继续嵌套...
});
});
});
Promise 链式调用
Promise 通过链式调用解决了嵌套问题,但仍然不够直观:
getUser(userId)
.then(user => getOrders(user.id))
.then(orders => getOrderDetail(orders[0].id))
.then(detail => console.log(detail))
.catch(err => console.error(err));
async/await
async/await 让异步代码读起来像同步代码一样自然:
async function getDetail(userId) {
const user = await getUser(userId);
const orders = await getOrders(user.id);
const detail = await getOrderDetail(orders[0].id);
console.log(detail);
}
基本语法
async 函数声明
async 关键字用于声明一个异步函数。async 函数总是返回一个 Promise:
// 函数声明
async function fetchData() {
return 'hello';
}
// 箭头函数
const fetchData = async () => {
return 'hello';
};
// 返回值自动包装为 Promise
fetchData().then(val => console.log(val)); // 'hello'
// 等价于
function fetchData() {
return Promise.resolve('hello');
}
await 表达式
await 只能在 async 函数内部使用,它会暂停函数执行,等待 Promise 完成并返回结果:
async function demo() {
// await 等待 Promise resolve,拿到结果值
const response = await fetch('/api/data');
const data = await response.json();
return data;
}
await 后面如果不是 Promise,会被自动包装成 Promise.resolve():
async function demo() {
const val = await 42; // 等价于 await Promise.resolve(42)
console.log(val); // 42
}
错误处理
try/catch
async/await 最大的优势之一是可以用标准的 try/catch 处理异步错误:
async function fetchUser(id) {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const user = await response.json();
return user;
} catch (error) {
console.error('获取用户失败:', error.message);
return null;
}
}
统一处理多个异步操作的错误
async function processOrder(orderId) {
try {
const order = await getOrder(orderId);
const payment = await processPayment(order);
const receipt = await sendReceipt(payment);
return receipt;
} catch (error) {
// 任何一步失败都会被捕获
console.error('订单处理失败:', error);
throw error;
}
}
不用 try/catch 的替代方案
对于不想用 try/catch 的场景,可以在 await 后直接 .catch():
async function fetchData() {
const data = await fetch('/api/data')
.then(res => res.json())
.catch(err => null); // 失败时返回 null
if (!data) {
console.log('请求失败,使用默认值');
}
return data;
}
并发控制
Promise.all:并行执行
当多个异步操作之间没有依赖关系时,应该并行执行:
// 错误:串行执行,浪费时间
async function getPageData() {
const user = await fetchUser(); // 等 1 秒
const posts = await fetchPosts(); // 再等 1 秒
const comments = await fetchComments(); // 再等 1 秒
// 总共 3 秒
}
// 正确:并行执行
async function getPageData() {
const [user, posts, comments] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchComments()
]);
// 总共约 1 秒(取决于最慢的那个)
}
Promise.allSettled:全部完成不论成败
async function batchFetch(urls) {
const results = await Promise.allSettled(
urls.map(url => fetch(url).then(r => r.json()))
);
const succeeded = results
.filter(r => r.status === 'fulfilled')
.map(r => r.value);
const failed = results
.filter(r => r.status === 'rejected')
.map(r => r.reason);
return { succeeded, failed };
}
控制并发数量
当需要限制同时进行的请求数时:
async function asyncPool(limit, items, fn) {
const results = [];
const executing = [];
for (const item of items) {
const p = fn(item);
results.push(p);
if (items.length >= limit) {
const e = p.then(() => executing.splice(executing.indexOf(e), 1));
executing.push(e);
if (executing.length >= limit) {
await Promise.race(executing);
}
}
}
return Promise.all(results);
}
// 最多同时 3 个请求
await asyncPool(3, urls, url => fetch(url));
与 Generator 的关系
async/await 本质上是 Generator + 自动执行器的语法糖:
// Generator 写法(需要手动执行器)
function* fetchDataGen() {
const response = yield fetch('/api/data');
const data = yield response.json();
return data;
}
// async/await 写法(内置执行器)
async function fetchData() {
const response = await fetch('/api/data');
const data = await response.json();
return data;
}
对应关系:
async function对应function*await对应yield- 内置自动执行器,无需手动调用
next()
常见陷阱
循环中的 await
在循环中使用 await 会导致串行执行:
// 问题:串行执行,每次循环都要等上一个完成
async function downloadAll(urls) {
const results = [];
for (const url of urls) {
const data = await fetch(url); // 一个接一个
results.push(await data.json());
}
return results;
}
// 解决:先发起所有请求,再统一等待
async function downloadAll(urls) {
const promises = urls.map(url => fetch(url).then(r => r.json()));
return Promise.all(promises);
}
forEach 中的 await 不生效
forEach 不会等待异步回调完成:
// 错误:forEach 不支持 async
[1, 2, 3].forEach(async (num) => {
await doSomething(num); // 不会按顺序执行
});
// 正确:使用 for...of
for (const num of [1, 2, 3]) {
await doSomething(num); // 串行执行
}
// 或者并行执行
await Promise.all([1, 2, 3].map(num => doSomething(num)));
忘记 await
async function save(data) {
// 错误:忘记 await,函数不会等待写入完成
writeToDatabase(data);
// 正确
await writeToDatabase(data);
}
不必要的 async
如果函数只是简单返回一个 Promise,不需要 async:
// 多余的 async
async function getUser(id) {
return fetch(`/api/users/${id}`).then(r => r.json());
}
// 简洁写法
function getUser(id) {
return fetch(`/api/users/${id}`).then(r => r.json());
}
小结
async/await 是 Generator 函数的语法糖,核心价值在于:
- 用同步的写法处理异步逻辑,代码更直观
- 原生支持 try/catch 错误处理
- 调试体验更好(堆栈信息更清晰)
使用建议:
- 无依赖的异步操作用
Promise.all并行执行 - 循环中的异步操作注意串行/并行的选择
- 合理使用 try/catch 处理可能的失败
- 不要在
forEach中使用 await,用for...of替代