【前端面试小册】JS-第6节:Promise 与 async/await 异常处理
一、Promise 异常捕获的两种方式
在 JavaScript 异步编程中,正确处理 Promise 异常至关重要。主要有两种方式:.catch() 方法和 async/await + try/catch。
方式一:使用 .catch() 捕获异常
function PromiseRejectTest() {
return new Promise((resolve, reject) => {
reject('抛出错误');
});
}
async function test() {
// ✅ 使用 .catch() 捕获 Promise 异常
PromiseRejectTest()
.catch(err => {
console.log(err); // 输出:抛出错误
});
}
test();
执行流程:
graph TD
A[PromiseRejectTest 被调用] --> B[返回 rejected Promise]
B --> C[.catch 捕获异常]
C --> D[执行 catch 回调]
D --> E[输出错误信息]
关键点:
.catch()会捕获 Promise 链中的任何错误- 即使没有
await,.catch()也能捕获 Promise 的 rejection
方式二:使用 await + try/catch 捕获异常
function PromiseRejectTest() {
return new Promise((resolve, reject) => {
reject('抛出错误');
});
}
async function test() {
try {
// ⚠️ 必须使用 await,否则异常无法被捕获
await PromiseRejectTest();
} catch(e) {
console.log('catch:', e); // 输出:catch: 抛出错误
}
}
test();
执行流程:
graph TD
A[test 函数执行] --> B[进入 try 块]
B --> C[await PromiseRejectTest]
C --> D[Promise rejected]
D --> E[异常抛出到 catch 块]
E --> F[执行 catch 处理]
关键点:
- 必须使用
await,否则 Promise 异常无法被try/catch捕获 await会将 Promise 的 rejection 转换为可被try/catch捕获的异常
二、Promise 异常捕获的陷阱
陷阱一:未使用 await 的 try/catch 捕获不到
function PromiseRejectTest() {
return new Promise((resolve, reject) => {
reject('抛出错误');
});
}
function test() {
try {
// ❌ 没有使用 await,异常无法被捕获
PromiseRejectTest();
} catch(e) {
// ❌ 不会执行
console.log('catch');
}
}
test();
原因分析:
graph TD
A[调用 PromiseRejectTest] --> B[返回 Promise 对象]
B --> C[Promise 状态变为 rejected]
C --> D[异常是异步抛出的]
D --> E[try 块已执行完毕]
E --> F[catch 无法捕获异步异常]
关键理解:
- Promise 的
reject是异步操作,不会同步抛出异常 try/catch只能捕获同步异常和await后的异步异常- 没有
await时,异常在 Promise 内部,无法被外部try/catch捕获
陷阱二:Promise 构造器中的 async 函数异常
// ❌ 错误示例:Promise 中直接使用 async 函数
new Promise(async (resolve) => {
// 等价于 reject('Error')
throw new Error('Error');
}).catch(e => {
// ❌ 不会执行,异常无法被捕获
console.log('catch');
});
原因分析:
// 上面的代码等价于下面的结构
new Promise((resolve1, reject1) => {
// async 函数返回一个新的 Promise
return new Promise((resolve2, reject2) => {
// 内层 Promise 抛出异常
reject2('抛出错误');
// 但异常并未向上传播给外层 Promise
});
}).catch(e => {
// 这里 catch 的是外层 Promise 的 reject1
// 但是异常并未抛给外层 Promise,所以 catch 不到
console.log('catch'); // ❌ 不会被执行
});
详细解释:
graph TD
A[new Promise 构造器] --> B[传入 async 函数作为 executor]
B --> C[async 函数自动返回 Promise]
C --> D[内层 Promise rejected]
D --> E[异常在内层 Promise]
E --> F[外层 Promise 无法捕获内层异常]
F --> G[catch 无法执行]
关键理解:
async函数本身就是异步的,会返回一个新的 Promise- Promise 构造器的 executor 如果是
async函数,相当于嵌套了两层 Promise - 内层 Promise 的异常不会自动向上传播到外层 Promise
正确的写法
// ✅ 方式一:在 async 函数内部处理异常
new Promise(async (resolve) => {
try {
throw new Error('Error');
} catch(e) {
reject(e); // 手动将异常传递给外层 Promise
}
}).catch(e => {
console.log('catch:', e); // ✅ 可以捕获
});
// ✅ 方式二:避免在 Promise 构造器中使用 async
new Promise((resolve, reject) => {
// 直接同步抛出,会被 try/catch 捕获
try {
throw new Error('Error');
} catch(e) {
reject(e); // 手动 reject
}
}).catch(e => {
console.log('catch:', e); // ✅ 可以捕获
});
// ✅ 方式三:直接使用 async 函数
async function asyncFunc() {
throw new Error('Error');
}
asyncFunc().catch(e => {
console.log('catch:', e); // ✅ 可以捕获
});
三、全局异常处理
浏览器环境
问题:浏览器中无法捕获未处理的 Promise rejection
// ❌ 这种方式异常会抛到全局,浏览器无法捕获
function test() {
PromiseRejectTest(); // 未使用 await,也没有 .catch()
}
test();
解决方式:始终添加 .catch() 或使用 await
// ✅ 方式一:添加 .catch()
PromiseRejectTest().catch(err => {
console.error('捕获到错误:', err);
});
// ✅ 方式二:使用 await + try/catch
async function test() {
try {
await PromiseRejectTest();
} catch(err) {
console.error('捕获到错误:', err);
}
}
Node.js 环境
Node.js 提供了全局异常捕获机制:
// ✅ 捕获未处理的 Promise rejection
process.on('unhandledRejection', (reason, promise) => {
console.error('未处理的 Promise rejection:', reason);
// 可以在这里记录日志、发送错误报告等
});
// ✅ 捕获未捕获的异常
process.on('uncaughtException', (error) => {
console.error('未捕获的异常:', error);
// 注意:应该尽快退出进程
process.exit(1);
});
// 示例
Promise.reject('未处理的错误'); // 会被 unhandledRejection 捕获
四、Promise 异常传播机制
异常传播规则
// Promise 链式调用中的异常传播
Promise.resolve()
.then(() => {
throw new Error('错误1');
})
.then(() => {
console.log('不会执行');
})
.catch(err => {
console.log('捕获到:', err.message); // 输出:捕获到: 错误1
throw new Error('错误2');
})
.catch(err => {
console.log('捕获到:', err.message); // 输出:捕获到: 错误2
});
传播规则:
- Promise 链中的异常会向下传播,直到遇到
.catch() .catch()中抛出异常,会继续向下传播- 如果链中没有
.catch(),异常会变为未处理的 rejection
异常传播流程图
graph TD
A[Promise 链开始] --> B[then 中抛出异常]
B --> C{是否有 catch}
C -->|有| D[catch 捕获异常]
C -->|无| E[异常继续传播]
E --> F{链中还有 catch}
F -->|有| G[下一个 catch 捕获]
F -->|无| H[未处理的 rejection]
D --> I[catch 中可以处理或继续抛出]
I --> J{是否继续抛出}
J -->|是| E
J -->|否| K[异常处理完毕]
五、最佳实践
1. 始终处理 Promise 异常
// ❌ 不好的做法:忽略异常
async function fetchData() {
const response = await fetch('/api/data');
return response.json();
}
// ✅ 好的做法:始终处理异常
async function fetchData() {
try {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch(error) {
console.error('获取数据失败:', error);
// 返回默认值或重新抛出
throw error;
}
}
2. 避免在 Promise 构造器中使用 async
// ❌ 不好的做法
new Promise(async (resolve, reject) => {
const data = await fetchData();
resolve(data);
});
// ✅ 好的做法:直接使用 async 函数
async function getData() {
const data = await fetchData();
return data;
}
// 或者使用 Promise.resolve().then()
Promise.resolve()
.then(async () => {
const data = await fetchData();
return data;
});
3. 使用 Promise.all 处理多个异步操作
// ✅ 并发请求,统一处理异常
async function fetchMultipleData() {
try {
const [users, posts, comments] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
]);
return { users, posts, comments };
} catch(error) {
console.error('获取数据失败:', error);
// 如果任何一个请求失败,都会进入 catch
throw error;
}
}
4. 添加全局异常处理
// 在应用入口添加全局异常处理
window.addEventListener('unhandledrejection', (event) => {
console.error('未处理的 Promise rejection:', event.reason);
// 可以在这里发送错误报告到监控平台
event.preventDefault(); // 阻止默认的错误输出
});
// 在 Node.js 中
process.on('unhandledRejection', (reason, promise) => {
console.error('未处理的 Promise rejection:', reason);
});
六、面试要点总结
核心知识点
-
异常捕获方式:
.catch()方法:适用于 Promise 链await + try/catch:必须在 async 函数中,且必须使用await
-
常见陷阱:
- 未使用
await的try/catch无法捕获 Promise 异常 - Promise 构造器中使用
async函数会导致异常无法被外层捕获
- 未使用
-
异常传播:
- Promise 链中的异常向下传播
- 未捕获的异常会变成未处理的 rejection
常见面试题
Q1: try/catch 能捕获 Promise 的异常吗?
答:需要分情况:
- ❌ 没有
await:无法捕获(因为异常是异步抛出的) - ✅ 有
await:可以捕获(await会将 Promise rejection 转换为异常)
Q2: 为什么 Promise 构造器中使用 async 函数会捕获不到异常?
答:因为 async 函数返回新的 Promise,形成了嵌套结构。内层 Promise 的异常不会自动传播到外层 Promise,需要在内部手动处理或使用 try/catch。
Q3: 如何避免未处理的 Promise rejection?
答:
- 始终为 Promise 添加
.catch()处理 - 在 async 函数中使用
try/catch - 在应用入口添加全局异常监听(
unhandledrejection事件)
实战建议
- ✅ 新代码优先使用
async/await + try/catch(更清晰) - ✅ 链式调用使用
.catch()(更简洁) - ✅ 避免在 Promise 构造器中使用
async函数 - ✅ 始终处理 Promise 异常,避免全局污染
前端面试小册 文章被收录于专栏
每天3-4节,跟着我50天学完,一起上岸! 靠它,我拿下阿里/百度/滴滴/字节/快手/腾讯/银行等offer 上岸银行,作为面试官使用,跳槽复习使用的小册
查看3道真题和解析
