async/await 是 ES2017 引入的语法糖,它让你能用写同步代码的方式去处理异步逻辑,彻底告别 Promise 的链式调用和回调地狱。
从 Promise 到 async/await
假设有一个分步异步操作:先获取用户信息,再根据用户 ID 获取订单列表。
传统 Promise 写法
function getUser() {
return fetch('/api/user').then(res => res.json());
}
function getOrders(userId) {
return fetch(`/api/orders?userId=${userId}`).then(res => res.json());
}
// 链式调用
getUser()
.then(user => {
console.log('用户:', user);
return getOrders(user.id);
})
.then(orders => {
console.log('订单:', orders);
})
.catch(err => {
console.error('出错了:', err);
});
同一个功能用 async/await 改写,逻辑立刻变得清晰:
async function loadUserOrders() {
try {
const user = await getUser(); // 等待用户数据
console.log('用户:', user);
const orders = await getOrders(user.id); // 等待订单数据
console.log('订单:', orders);
} catch (err) {
console.error('出错了:', err);
}
}
loadUserOrders();
async关键字标记函数为异步函数,它永远返回一个 Promise。await会暂停当前 async 函数的执行,等待后面的 Promise 完成,并返回其结果。- 如果 Promise 被拒绝,
await会抛出错误,可以用try...catch捕获。
代码阅读顺序,就是执行顺序
这才是 async/await 最实用的价值——从上到下读代码,逻辑一目了然,不再需要来回跳转 .then() 块。对于复杂的条件分支、循环异步操作,优势更加明显。
// 根据用户权限决定下一步操作
async function dashboard(userId) {
const user = await fetchUser(userId);
if (user.role === 'admin') {
const reports = await fetchReports();
return { user, reports };
} else {
const tasks = await fetchTasks(userId);
return { user, tasks };
}
}
如果换成 Promise,就需要在多个 .then() 里嵌套条件,可读性急剧下降。
错误处理:用 try...catch 统一捕获
同步风格的另一个好处就是错误处理回归经典模式。无论是网络错误、JSON 解析错误,还是业务抛出的异常,都能在同一个 catch 块中处理。
async function safeFetch(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('请求失败:', error.message);
// 可以返回兜底数据或重新抛出
return null;
}
}
实际开发中常用的“坑”和最佳实践
1. 不要在循环中盲目使用 await
如果循环中的每次迭代不相互依赖,同步等待会不必要地串行,延长总耗时。
// ❌ 依次等待,总耗时 = 每次耗时累加
for (const id of ids) {
const data = await fetchData(id);
}
// ✅ 并行执行,总耗时 ≈ 最慢的那一个
const promises = ids.map(id => fetchData(id));
const results = await Promise.all(promises);
2. await 只能在 async 函数内使用
顶级作用域(例如模块顶层)在没有包裹 async 函数时不能直接写 await。不过,现代 JavaScript 已经支持了 top-level await(ES2022),在 ES Module 中可以直接在模块顶层使用 await,无需封装。
3. async 函数永远返回 Promise
即使你 return 一个普通值,它也会被自动包装成 Promise.resolve(value)。这意味着调用方仍然需要 await 或 .then() 来获取结果,否则得到的是一个 Promise 对象。
4. 忘记 await 的常见错误
如果忘了写 await,得到的只会是一个 Pending 状态的 Promise 对象,而不是实际的返回值。
const user = getUser(); // user 是 Promise 对象,不是数据
console.log(user.name); // undefined
总结
async/await 本质上是 Promise + Generator 的语法糖,并没有带来全新的异步机制,但它极大提升了异步代码的可读性和可维护性。在实际开发中,它能让你用最直观的方式编排异步流程,让业务逻辑回归线性思维,而不必在回调链中来回跳转。