上一期我们学会了用 Promise 链式调用来摆脱回调地狱。
今天我们再往前迈一步,用 async/await 把异步代码写得像同步代码一样直观。
async/await 是 ES2017 引入的语法糖,本质上还是 Promise,但极大提升了代码的可读性。
1. 基本写法
javascript
// 普通 Promise 写法
function fetchUser() {
return fetch('/api/user')
.then(res => res.json())
.then(data => data.user);
}
// async/await 写法
async function fetchUser() {
const res = await fetch('/api/user');
const data = await res.json();
return data.user;
}
关键点:
- 在函数前加
async,这个函数就自动返回一个 Promise - 在 Promise 前面加
await,表示"等到这个 Promise 完成再继续往下走" - 代码从上到下顺序执行,像写同步代码一样
2. 错误处理:使用 try...catch
javascript
async function loginFlow(username, password) {
try {
const token = await login(username, password);
const user = await getUserInfo(token);
const products = await getRecommendations(user.level);
renderProducts(products);
showSuccess("欢迎回来!");
} catch (error) {
console.error("登录流程失败:", error);
showError("出错了,请稍后重试");
}
}
优点:
- 只需要一个
try...catch,就能捕获整个流程中任意一步的错误 - 错误位置清晰,栈追踪更有意义
- 比 Promise 的
.catch()更接近我们熟悉的同步错误处理方式
3. 常见使用场景
3.1 顺序执行多个异步操作
javascript
async function processOrder() {
const order = await getOrderFromDB(orderId);
const payment = await processPayment(order.amount);
const shipping = await createShippingLabel(payment);
await sendConfirmationEmail(shipping.trackingNumber);
return shipping;
}
3.2 与 Promise.all 结合实现并发
javascript
async function loadUserData(userId) {
const [profile, posts, friends] = await Promise.all([
fetch(`/api/profile/${userId}`),
fetch(`/api/posts/${userId}`),
fetch(`/api/friends/${userId}`)
]);
// 三个请求并发发起,最后一起等待完成
const profileData = await profile.json();
const postsData = await posts.json();
const friendsData = await friends.json();
return { profileData, postsData, friendsData };
}
3.3 在循环中使用 await(注意性能)
javascript
// 顺序执行(适合需要前一步结果的情况)
async function processItems(items) {
for (const item of items) {
const result = await processSingleItem(item);
saveResult(result);
}
}
// 如果不需要顺序,可以先 Promise.all 再循环
async function processItemsInParallel(items) {
const promises = items.map(item => processSingleItem(item));
const results = await Promise.all(promises);
results.forEach(saveResult);
}
4. 常见陷阱与注意事项
| 问题 | 错误写法 | 正确写法 | 说明 |
|---|---|---|---|
| 在循环里 await 导致串行慢 | forEach(item => await fn(item)) |
用 for...of 或 Promise.all |
forEach 不等待 |
| 忘记 await | const data = fetch(url) |
const data = await fetch(url) |
否则得到 Promise 对象 |
| try...catch 范围太小 | 只 catch 单行 | 包住整个流程 | 否则错误漏掉 |
| 顶层 await | 不支持(旧环境) | 包在 async 函数里 | 现代模块支持顶层 await |
| 性能误用 | 所有操作都 await | 能并行的用 Promise.all | 避免不必要的等待 |
5. 真实业务对比
Promise 链式:
js
login(username, pwd)
.then(token => getUserInfo(token))
.then(user => getRecommendations(user.level))
.then(products => render(products))
.catch(err => showError(err));
async/await:
js
async function start() {
try {
const token = await login(username, pwd);
const user = await getUserInfo(token);
const products = await getRecommendations(user.level);
render(products);
} catch (err) {
showError(err);
}
}
大多数开发者认为后者更清晰、更容易维护。
6. 小结:async/await 的核心价值
- 可读性:代码结构接近日常同步思维
- 错误处理:统一的 try...catch
- 调试友好:断点更容易命中预期位置
- 与 Promise 完全兼容:该并发时用 Promise.all,该顺序时用 await
一句话总结 :
async/await 让异步代码看起来像同步代码,但依然保留了异步非阻塞的本质。
下一期我们将把学到的异步知识应用到实际网络请求中:
Fetch API 与异步网络请求 ------ 现代浏览器中最常用的数据获取方式。
我们下期见~
留言区互动:
你更喜欢 Promise 链式还是 async/await?
有没有在项目中因为 async/await 写法而修复过 bug 的经历?