引言:为什么 Async/Await 成为异步编程的首选?
**
在 JavaScript 世界中,异步编程是绕不开的核心话题。从早期的回调函数,到 ES6 的 Promise,再到 ES8 推出的 Async/Await,每一次演进都在解决前一代方案的痛点。
- 回调函数:陷入 “回调地狱”,代码可读性差、维护成本高
- Promise:解决了回调地狱,但链式调用的 then 依然不够直观,错误处理需要嵌套
- Async/Await:基于 Promise 封装,用同步的语法写异步代码,可读性拉满,错误处理更优雅
本文将从基础到进阶,全面解析 Async/Await 的用法、原理、调优技巧,无论是新手还是老手,都能从中获得收获。
一、新手必学:Async/Await 基础用法
1. 核心语法规则
Async/Await 由两个关键字组成,必须搭配使用:
// 1. 用 async 声明异步函数
async function fetchData() {
// 2. 用 await 等待 Promise 完成(只能在 async 函数内使用)
const result = await new Promise((resolve) => {
setTimeout(() => resolve("请求成功"), 1000);
});
console.log(result); // 1秒后输出:请求成功
}
fetchData();
关键特性:
- async 函数的返回值必然是 Promise(无论是否显式返回)
async function test() {
return "hello"; // 等价于 return Promise.resolve("hello")
}
test().then(res => console.log(res)); // hello
- await 会 “暂停” 函数执行,直到 Promise 状态变为 fulfilled 或 rejected
- await 后面可以跟任意值(非 Promise 会直接转为 resolved 状态)
2. 错误处理:try/catch 优雅捕获
Async/Await 用同步的错误处理方式(try/catch)替代 Promise 的 catch 方法,更符合直觉:
async function fetchDataWithError() {
try {
const result = await new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("请求失败")), 1000);
});
console.log(result); // 不会执行
} catch (error) {
console.error(error.message); // 输出:请求失败
}
}
fetchDataWithError();
批量错误处理技巧:
如果多个异步操作互不依赖,可用 Promise.all 配合 try/catch:
async function fetchMultipleData() {
try {
// 并行执行多个异步操作(比串行更高效)
const [data1, data2] = await Promise.all([
fetch("/api/user"),
fetch("/api/posts")
]);
console.log("所有请求成功", data1, data2);
} catch (error) {
console.error("至少一个请求失败", error);
}
}
3. 与 Promise 的兼容性
Async/Await 是 Promise 的 “语法糖”,完全兼容 Promise 生态:
// 现有 Promise 代码可直接迁移
function oldPromiseFunc() {
return new Promise((resolve) => {
setTimeout(() => resolve("旧接口"), 500);
});
}
// 用 await 直接调用
async function newAsyncFunc() {
const data = await oldPromiseFunc();
console.log(data); // 旧接口
}
二、老手必备:Async/Await 进阶调优
1. 性能优化:避免串行陷阱
新手常犯的错误:不必要的串行执行,导致性能浪费:
// 反面示例:串行执行(总耗时 = 1s + 1s = 2s)
async function badPerformance() {
const data1 = await fetch("/api/data1"); // 1s
const data2 = await fetch("/api/data2"); // 1s(需等待 data1 完成)
}
优化方案:
- 互不依赖的异步操作:用 Promise.all 并行执行(总耗时 = 最长单个任务时间)
async function goodPerformance() {
// 先启动所有异步操作
const promise1 = fetch("/api/data1");
const promise2 = fetch("/api/data2");
// 再等待所有结果
const [data1, data2] = await Promise.all([promise1, promise2]); // 总耗时 1s
}
- 部分依赖的异步操作:用 Promise.race 或 Promise.allSettled
// 场景:优先使用缓存数据,超时后请求接口
async function fetchWithCache() {
const cachePromise = getCache(); // 快速获取缓存(0.1s)
const apiPromise = fetch("/api/data").then(res => res.json()); // 1s
// 谁先完成用谁的结果
const result = await Promise.race([cachePromise, apiPromise]);
return result;
}
2. 错误处理进阶:精准捕获多个异步错误
Promise.all 会 “快速失败”(一个失败则整体失败),如果需要捕获每个异步操作的错误,用 Promise.allSettled:
async function fetchAllWithDetails() {
const results = await Promise.allSettled([
fetch("/api/user"),
fetch("/api/invalid-url"), // 无效接口
fetch("/api/posts")
]);
// 遍历结果,分别处理成功和失败
results.forEach((result, index) => {
if (result.status === "fulfilled") {
console.log(`接口${index+1}成功`, result.value);
} else {
console.log(`接口${index+1}失败`, result.reason);
}
});
}
3. 异步函数的返回值处理技巧
- 返回 Promise 链:实现链式调用
async function getUser() {
const res = await fetch("/api/user");
return res.json(); // 返回 Promise,可继续 then
}
getUser()
.then(user => fetch(`/api/posts?userId=${user.id}`))
.then(posts => console.log(posts));
- 中断异步流程:抛出错误或返回 Promise.reject
async function validateUser(user) {
if (!user.id) {
// 抛出错误会被 catch 捕获
throw new Error("用户ID不存在");
// 等价于 return Promise.reject(new Error("用户ID不存在"))
}
return user;
}
4. 与 Generator 函数的对比(老手视角)
Async/Await 本质上是 Generator 函数的语法糖,但更简洁、功能更强:
| 特性 | Generator 函数 | Async/Await |
|---|---|---|
| 执行触发 | 需要手动调用 next () | 直接调用,自动执行 |
| 错误处理 | 需要手动传递错误 | 原生支持 try/catch |
| 可读性 | 需配合 co 库,较复杂 | 同步语法,直观易懂 |
| 返回值 | Iterator 对象 | Promise 对象 |
示例对比:
// Generator 函数(需 co 库)
const co = require("co");
function* generatorFunc() {
const data = yield fetch("/api/data");
return data.json();
}
co(generatorFunc()).then(res => console.log(res));
// Async/Await 函数(原生支持)
async function asyncFunc() {
const data = await fetch("/api/data");
return data.json();
}
asyncFunc().then(res => console.log(res));
三、实战场景:Async/Await 的最佳实践
1. 接口请求封装(前后端通用)
// 封装请求工具函数
async function request(url, options = {}) {
try {
const res = await fetch(url, {
method: options.method || "GET",
headers: {
"Content-Type": "application/json",
...options.headers
},
body: options.body ? JSON.stringify(options.body) : undefined
});
// 统一处理 HTTP 错误(4xx/5xx)
if (!res.ok) {
throw new Error(`HTTP错误:${res.status}`);
}
return await res.json();
} catch (error) {
// 统一错误日志上报
console.error("请求失败:", error);
// 重新抛出错误,让调用方处理
throw error;
}
}
// 调用示例
async function fetchUserInfo() {
try {
const user = await request("/api/user/1", { method: "GET" });
console.log("用户信息:", user);
} catch (error) {
// 业务层错误处理(如提示用户)
alert("获取用户信息失败,请重试");
}
}
2. 异步迭代(处理批量异步任务)
场景:分页加载数据,直到没有更多数据为止:
async function fetchAllPages() {
let page = 1;
let allData = [];
let hasMore = true;
while (hasMore) {
try {
const data = await request(`/api/data?page=${page}&size=10`);
allData = [...allData, ...data.list];
hasMore = data.hasMore;
page++;
} catch (error) {
console.error("加载分页失败", error);
// 失败后重试机制
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
return allData;
}
3. 结合定时器的异步控制
场景:接口请求超时处理:
async function fetchWithTimeout(url, timeout = 5000) {
// 创建超时 Promise
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error("请求超时")), timeout);
});
// 用 Promise.race 实现超时控制
const result = await Promise.race([
request(url),
timeoutPromise
]);
return result;
}
四、避坑指南:新手常犯的 5 个错误
1. 忘记 await 导致异步变同步
// 错误示例:没有 await,直接返回 Promise 对象
async function wrongUsage() {
const data = fetch("/api/data"); // 错误:缺少 await
console.log(data); // Promise { <pending> }
}
// 正确示例
async function correctUsage() {
const data = await fetch("/api/data");
console.log(data); // Response 对象
}
2. 在非 async 函数中使用 await
// 错误:SyntaxError: await is only valid in async functions
function notAsyncFunc() {
const data = await fetch("/api/data");
}
3. 滥用 Promise.all 处理依赖关系
// 错误:data2 依赖 data1 的结果,却用 Promise.all 并行执行
async function wrongDependency() {
const [data1, data2] = await Promise.all([
fetch("/api/user"),
fetch(`/api/posts?userId=${data1.id}`) // data1 未定义,报错
]);
}
// 正确:串行执行依赖任务
async function correctDependency() {
const data1 = await fetch("/api/user");
const data2 = await fetch(`/api/posts?userId=${data1.id}`);
}
4. 忽略错误处理导致程序崩溃
// 错误:未捕获 await 抛出的错误,导致函数执行中断
async function noErrorHandling() {
const data = await fetch("/api/invalid-url"); // 失败会抛出错误
console.log(data); // 不会执行
}
5. 同步循环中执行异步操作
// 错误:for 循环中直接 await,导致串行执行(耗时过长)
async function wrongLoop() {
const ids = [1, 2, 3];
const result = [];
for (const id of ids) {
const data = await fetch(`/api/data/${id}`); // 串行执行,总耗时 3s
result.push(data);
}
}
// 正确:先收集所有 Promise,再并行执行
async function correctLoop() {
const ids = [1, 2, 3];
const promises = ids.map(id => fetch(`/api/data/${id}`));
const result = await Promise.all(promises); // 并行执行,总耗时 1s
}
五、原理深挖:Async/Await 是如何工作的?
核心原理:
Async/Await 基于 Promise 和 Generator 实现:
- async 函数本质是一个返回 Promise 的 Generator 函数的语法糖
- 浏览器 / Node.js 内置了类似 co 库的自动执行器,无需手动调用 next()
- await 关键字相当于 Generator 函数中的 yield,用于暂停和恢复执行
简化实现模拟:
// 模拟 async/await 的自动执行器
function myAsync(generatorFunc) {
return new Promise((resolve, reject) => {
const generator = generatorFunc();
function runNext(result) {
const { value, done } = generator.next(result);
if (done) {
// 生成器执行完毕,resolve 结果
return resolve(value);
}
// 等待 Promise 完成后继续执行
Promise.resolve(value).then(
res => runNext(res),
err => reject(err)
);
}
runNext();
});
}
// 使用模拟的 async 函数
const fetchData = myAsync(function* () {
const data = yield fetch("/api/data");
return data.json();
});
六、总结:Async/Await 的核心优势
- 可读性极强:同步语法写异步代码,降低认知成本
- 错误处理优雅:try/catch 统一捕获所有错误,无需嵌套
- 兼容性好:基于 Promise,完全兼容现有异步生态
- 调试友好:断点调试时,能像同步代码一样逐行执行
- 代码简洁:省去大量 then 链式调用,减少模板代码
无论是新手入门异步编程,还是老手重构现有代码,Async/Await 都是最优选择。掌握本文的基础用法、调优技巧和避坑指南,就能轻松应对各类异步场景。