JS 入门通关手册(43):async/await 原理与异常处理(实战 + 面试,彻底搞懂)
本文深入解析 async/await 的底层原理(Promise + Generator 语法糖)、基础语法与关键注意点,重点讲解 async/await 的异常处理技巧(统一捕获、精准捕获),结合串行请求、并行请求、重试机制等真实业务场景,对比 async/await 与 Promise 链式调用的优劣,整理高频面试题标准答案,帮助开发者彻底掌握 async/await,写出更简洁、易维护、易调
摘要
本文深入解析 async/await 的底层原理、语法规范、异常处理技巧,结合大量真实业务场景,拆解 async/await 与 Promise、Generator 的关联,提供优雅的异常捕获方案,对比 Promise 链式调用的优劣,覆盖面试高频考点(原理、异常处理、串行 / 并行实践),帮助开发者摆脱回调嵌套与链式调用的繁琐,写出更简洁、易维护的异步代码。
一、前言:async/await 为什么能替代 Promise 链式调用?
Promise 的出现解决了回调地狱,但当异步操作较多时,链式调用(then 链)依然会显得繁琐,代码可读性、可维护性不足,且异常处理需要层层 catch,不够优雅。
Promise 链式调用的痛点示例
javascript
运行
// 串行请求:获取用户 → 获取订单 → 获取详情
getRequest("/api/user")
.then((user) => getRequest(`/api/order?userId=${user.id}`))
.then((order) => getRequest(`/api/detail?orderId=${order.id}`))
.then((detail) => {
console.log(detail);
// 后续逻辑...
})
.catch((err) => {
console.log("请求错误:", err);
});
虽然比回调地狱简洁,但依然需要不断嵌套 then,逻辑连贯性差,异常捕获只能统一处理(无法精准捕获单个请求错误)。
async/await 的优势
async/await 是 Promise + Generator 函数的语法糖,它的核心作用是:用同步代码的写法,实现异步操作,彻底摆脱 then 链,代码更简洁、逻辑更清晰,异常处理更灵活。
改造后(async/await 版本):
javascript
运行
// 同步写法,实现异步逻辑
async function getDetail() {
try {
const user = await getRequest("/api/user");
const order = await getRequest(`/api/order?userId=${user.id}`);
const detail = await getRequest(`/api/detail?orderId=${order.id}`);
console.log(detail);
} catch (err) {
console.log("请求错误:", err);
}
}
代码完全是同步的结构,却能执行异步操作,可读性大幅提升,异常处理也更灵活。
二、async/await 基础语法(必背)
1. 核心定义
- async:用于修饰函数,标记该函数是一个异步函数,函数执行后永远返回一个 Promise(无论函数内部返回什么值,都会被转为
Promise.resolve(值))。 - await:只能用在 async 函数内部,用于等待一个 Promise 完成(fulfilled 或 rejected),暂停函数执行,直到 Promise 有结果后,再继续执行后续代码。
2. 基础用法(3 个核心示例)
示例 1:基本使用(成功场景)
javascript
运行
// async 修饰函数,返回 Promise
async function fn() {
// await 等待 Promise 成功,获取结果
const res = await Promise.resolve("异步操作成功");
console.log(res); // 异步操作成功
return res; // 等同于 return Promise.resolve(res)
}
// 调用 async 函数
fn().then((res) => console.log("函数返回结果:", res)); // 函数返回结果:异步操作成功
示例 2:await 等待失败的 Promise
javascript
运行
async function fn() {
try {
// await 等待 Promise 失败,会抛出错误,需用 try/catch 捕获
const res = await Promise.reject(new Error("异步操作失败"));
console.log(res); // 不会执行(因为前面抛出错误)
} catch (err) {
console.log("捕获错误:", err.message); // 捕获错误:异步操作失败
}
}
fn();
示例 3:async 函数返回值自动转为 Promise
javascript
运行
// 1. 返回普通值 → 转为 Promise.resolve(普通值)
async function fn1() {
return 100;
}
fn1().then((res) => console.log(res)); // 100
// 2. 返回 Promise → 直接返回该 Promise
async function fn2() {
return Promise.resolve(200);
}
fn2().then((res) => console.log(res)); // 200
// 3. 抛出错误 → 转为 Promise.reject(错误)
async function fn3() {
throw new Error("主动抛出错误");
}
fn3().catch((err) => console.log(err.message)); // 主动抛出错误
3. 关键注意点(面试避坑)
- await 只能用在 async 函数内部,普通函数中使用会报错(语法错误)。
- async 函数执行时,遇到 await 会暂停执行(不会阻塞整个 JS 线程,只是暂停当前函数),直到 Promise 完成。
- await 后面可以跟任意值:如果是 Promise,等待其结果;如果是非 Promise,直接返回该值(等同于
await Promise.resolve(值))。 - async 函数的返回值,永远是 Promise(即使没有 return 语句,也会返回
Promise.resolve(undefined))。
三、async/await 底层原理(面试必考)
核心结论
async/await 本质是 Generator 函数 + Promise 的语法糖,浏览器 / Node.js 内部会自动执行 Generator 函数,无需手动调用 next () 方法,简化了 Generator 函数的使用。
1. 先了解 Generator 函数(基础铺垫)
Generator 函数是 ES6 推出的异步编程方案,核心是 “可暂停、可恢复”,用 function* 定义,用 yield 暂停执行,用 next() 恢复执行。
示例(Generator 实现异步):
javascript
运行
// Generator 函数(带 *)
function* gen() {
yield Promise.resolve(1); // 暂停,返回 Promise
yield Promise.resolve(2); // 暂停,返回 Promise
return 3;
}
// 手动执行 Generator 函数
const g = gen();
g.next().value.then((res) => {
console.log(res); // 1
return g.next().value;
}).then((res) => {
console.log(res); // 2
return g.next().value;
}).then((res) => {
console.log(res); // 3
});
可以看到,Generator 函数需要手动调用 next (),且需要结合 then 链处理异步,使用繁琐 —— 这就是 async/await 要解决的问题。
2. async/await 与 Generator 的关联
async 函数本质上是对 Generator 函数的 “自动执行封装”,相当于:
- 用 async 替代
function* - 用 await 替代
yield - 自动执行 Generator 函数的 next () 方法,无需手动调用
- 自动处理 Promise 结果,将结果返回给 await,无需手动 then
3. 手动模拟 async/await(面试加分)
用 Promise 自动执行 Generator 函数,模拟 async/await 的底层逻辑:
javascript
运行
// 模拟 async/await 的自动执行器
function myAsync(genFn) {
// 返回一个 Promise(对应 async 函数的返回值)
return new Promise((resolve, reject) => {
const gen = genFn(); // 执行 Generator 函数,得到迭代器
// 自动执行 next()
function next(res) {
const result = gen.next(res); // 执行 next,获取 { value, done }
if (result.done) {
// Generator 执行完毕,resolve 最终结果
return resolve(result.value);
}
// 若 value 是 Promise,等待其完成后,继续执行 next
Promise.resolve(result.value).then(
(val) => next(val), // 成功:将结果传给下一个 yield
(err) => reject(err) // 失败:直接 reject
);
}
// 启动自动执行
next();
});
}
// 测试模拟的 async/await
function* gen() {
const a = yield Promise.resolve(1);
const b = yield Promise.resolve(a + 1);
return b + 1;
}
// 用 myAsync 模拟 async/await 调用
myAsync(gen).then((res) => console.log(res)); // 3
通过这个模拟,能清晰看到:async/await 就是帮我们自动执行 Generator 函数,自动处理 Promise 结果,简化了手动调用的繁琐操作。
面试必答:async/await 与 Promise 的关系
- async/await 是基于 Promise 实现的,不能脱离 Promise 单独使用(await 后面必须是 Promise 或可转为 Promise 的值)。
- async 函数的返回值是 Promise,因此可以继续用 then/catch 处理结果。
- async/await 是 Promise 的 “语法糖”,没有新增异步机制,只是简化了 Promise 的使用方式。
四、async/await 异常处理(实战重点)
async/await 的异常处理,核心是 try/catch 语句,比 Promise 的 catch 更灵活,可以精准捕获单个异步操作的错误,也可以统一捕获所有错误。
1. 统一捕获所有错误(简单场景)
用 try 包裹所有 await 操作,catch 捕获所有异步错误(包括单个 await 的失败):
javascript
运行
async function fetchData() {
try {
// 所有 await 操作都在 try 中
const user = await getRequest("/api/user");
const order = await getRequest(`/api/order?userId=${user.id}`);
const detail = await getRequest(`/api/detail?orderId=${order.id}`);
console.log(detail);
} catch (err) {
// 统一捕获所有错误(任何一个 await 失败,都会进入 catch)
console.log("捕获错误:", err.message);
// 可做错误处理(如提示用户、重试请求)
}
}
2. 精准捕获单个错误(复杂场景)
如果需要对不同的异步操作,做不同的错误处理,可给每个 await 单独添加 try/catch:
javascript
运行
async function fetchData() {
let user = null;
// 捕获获取用户的错误
try {
user = await getRequest("/api/user");
} catch (err) {
console.log("获取用户失败:", err.message);
return; // 失败后,终止后续操作
}
let order = null;
// 捕获获取订单的错误
try {
order = await getRequest(`/api/order?userId=${user.id}`);
} catch (err) {
console.log("获取订单失败:", err.message);
// 可做重试逻辑
order = await getRequest(`/api/order?userId=${user.id}`);
}
// 捕获获取详情的错误
try {
const detail = await getRequest(`/api/detail?orderId=${order.id}`);
console.log(detail);
} catch (err) {
console.log("获取详情失败:", err.message);
}
}
3. 结合 Promise.catch 处理错误(补充方案)
await 后面的 Promise 可以直接调用 catch,捕获单个异步操作的错误,不影响后续代码执行:
javascript
运行
async function fetchData() {
// 单个 await 错误捕获(不影响后续 await 执行)
const user = await getRequest("/api/user").catch((err) => {
console.log("获取用户失败:", err.message);
return null; // 失败后返回默认值,避免后续代码报错
});
if (!user) return; // 若获取用户失败,终止后续操作
const order = await getRequest(`/api/order?userId=${user.id}`).catch((err) => {
console.log("获取订单失败:", err.message);
return null;
});
if (!order) return;
const detail = await getRequest(`/api/detail?orderId=${order.id}`);
console.log(detail);
}
4. 面试避坑:常见异常处理误区
- 误区 1:忘记用 try/catch,await 失败会导致整个 async 函数返回 rejected Promise,且错误会 “冒泡”,若未捕获,会触发 Uncaught Error。
- 误区 2:多个 await 并行执行时,用 try/catch 统一捕获,无法区分哪个 await 失败(需单独捕获或结合 Promise.all 处理)。
- 误区 3:认为 await 会阻塞整个 JS 线程 —— 其实不会,await 只暂停当前 async 函数,JS 线程会继续执行其他同步代码。
五、async/await 实战场景(真实项目常用)
场景 1:异步串行执行(按顺序执行多个异步操作)
最常用场景:多个异步操作有依赖关系(如先获取用户,再根据用户 ID 获取订单)
javascript
运行
// 串行请求:用户 → 订单 → 详情
async function getOrderDetail() {
try {
// 1. 获取用户
const { data: user } = await axios.get("/api/user");
// 2. 根据用户ID获取订单(依赖用户数据)
const { data: order } = await axios.get(`/api/order?userId=${user.id}`);
// 3. 根据订单ID获取详情(依赖订单数据)
const { data: detail } = await axios.get(`/api/detail?orderId=${order.id}`);
// 渲染数据
renderDetail(detail);
return detail;
} catch (err) {
console.log("请求失败:", err);
ElMessage.error("获取数据失败,请重试"); // 结合UI组件提示
}
}
场景 2:异步并行执行(同时执行多个异步操作,提高效率)
场景:多个异步操作无依赖关系(如同时获取首页多个模块的数据),用 Promise.all 结合 async/await 实现并行:
javascript
运行
// 并行请求:同时获取首页轮播、推荐、公告数据
async function getHomeData() {
try {
// 用 Promise.all 并行执行多个请求
const [bannerRes, recommendRes, noticeRes] = await Promise.all([
axios.get("/api/banner"),
axios.get("/api/recommend"),
axios.get("/api/notice"),
]);
// 分别处理数据
const banner = bannerRes.data;
const recommend = recommendRes.data;
const notice = noticeRes.data;
// 渲染首页
renderHome(banner, recommend, notice);
} catch (err) {
console.log("首页数据请求失败:", err);
}
}
⚠️ 注意:并行执行时,只要有一个请求失败,Promise.all 会立即失败,进入 catch;若需允许部分失败,可用 Promise.allSettled。
场景 3:异步并行 + 部分容错(允许部分请求失败)
javascript
运行
async function getHomeData() {
// 用 allSettled 并行执行,获取所有结果
const results = await Promise.allSettled([
axios.get("/api/banner"),
axios.get("/api/recommend"),
axios.get("/api/notice"),
]);
// 筛选成功的结果,处理失败的结果
const banner = results[0].status === "fulfilled" ? results[0].value.data : [];
const recommend = results[1].status === "fulfilled" ? results[1].value.data : [];
const notice = results[2].status === "fulfilled" ? results[2].value.data : [];
// 即使部分失败,也渲染首页(用默认值替代失败数据)
renderHome(banner, recommend, notice);
// 记录失败的请求
const failRequests = results.filter(item => item.status === "rejected");
if (failRequests.length > 0) {
console.log("部分请求失败:", failRequests);
}
}
场景 4:异步重试机制(失败后自动重试)
javascript
运行
// 封装带重试的请求函数
async function requestWithRetry(url, retryCount = 3, delay = 1000) {
try {
const { data } = await axios.get(url);
return data;
} catch (err) {
// 重试次数大于0,继续重试
if (retryCount > 0) {
console.log(`请求失败,剩余重试次数:${retryCount},${delay}ms后重试`);
// 延迟重试(用 setTimeout 包裹,返回 Promise)
await new Promise(resolve => setTimeout(resolve, delay));
return requestWithRetry(url, retryCount - 1, delay); // 递归重试
}
// 重试次数耗尽,抛出错误
throw new Error(`请求失败,已重试${3}次:${err.message}`);
}
}
// 使用
async function fetchData() {
try {
const data = await requestWithRetry("/api/data", 3, 1000);
console.log(data);
} catch (err) {
console.log(err.message);
}
}
六、async/await vs Promise 链式调用(面试必比)
表格
| 对比项 | async/await | Promise 链式调用 |
|---|---|---|
| 代码结构 | 同步写法,逻辑连贯 | 链式嵌套,可读性一般 |
| 异常处理 | try/catch 灵活捕获(单个 / 统一) | 只能链式 catch,精准捕获繁琐 |
| 调试体验 | 可直接断点调试,步骤清晰 | 断点调试需跟进 then 链,不够直观 |
| 学习成本 | 低(同步思维,易理解) | 中(需理解链式调用和状态流转) |
| 适用场景 | 复杂异步逻辑、多依赖异步操作 | 简单异步操作、少量链式调用 |
总结建议
- 简单异步操作(如单个接口请求):用 Promise 或 async/await 均可,看个人习惯。
- 复杂异步逻辑(多依赖、多步骤、需精准异常处理):优先用 async/await,代码更简洁、易维护、易调试。
- 并行异步操作:结合 Promise.all/allSettled + async/await,兼顾效率和可读性。
七、高频面试题(必背标准答案)
-
async/await 是什么?底层原理是什么?答:async/await 是 Promise + Generator 函数的语法糖,用于用同步代码的写法实现异步操作;底层是通过自动执行 Generator 函数,结合 Promise 处理异步结果,简化了 Generator 函数的手动调用。
-
async 函数的返回值是什么?答:async 函数的返回值永远是一个 Promise;如果函数内部返回普通值,会转为 Promise.resolve (普通值);如果抛出错误,会转为 Promise.reject (错误)。
-
await 只能用在什么地方?如果 await 后面跟的不是 Promise,会怎么样?答:await 只能用在 async 函数内部;如果 await 后面跟的不是 Promise,会直接返回该值,等同于 await Promise.resolve (值)。
-
async/await 如何处理异常?有几种方式?答:主要用 try/catch 处理异常,有两种方式:① 统一捕获(try 包裹所有 await,catch 统一处理);② 精准捕获(每个 await 单独 try/catch,或结合 Promise.catch)。
-
async/await 和 Promise 链式调用的区别?各自的优缺点?答:async/await 是同步写法,逻辑连贯、异常处理灵活、调试方便,但依赖 async 函数;Promise 链式调用是异步写法,可读性一般、异常捕获繁琐,但无需依赖 async 函数,兼容性更好(低版本浏览器需兼容)。
-
async/await 会阻塞 JS 线程吗?为什么?答:不会;await 只会暂停当前 async 函数的执行,不会阻塞整个 JS 线程,JS 线程会继续执行其他同步代码,直到 await 等待的 Promise 完成,再恢复当前 async 函数的执行。
八、总结
- async/await 是 Promise + Generator 的语法糖,核心是 “同步写法实现异步操作”,彻底简化异步代码。
- async 修饰函数,返回 Promise;await 只能用在 async 内部,等待 Promise 完成并返回结果。
- 异常处理核心是 try/catch,可实现统一捕获或精准捕获,比 Promise 链式调用更灵活。
- 实战中,串行异步用 await 顺序执行,并行异步用 Promise.all + await,提高效率。
- 掌握其底层原理(Generator + Promise)和异常处理技巧,是前端面试的必备能力。
更多推荐

所有评论(0)