异步错误处理
解释 JavaScript 中异步场景的错误处理机制,包括为什么 try/catch 抓不到异步错误以及如何正确处理。
#tech / dev / frontend
#type / concept
#status / growing
#resource / javascript
#resource / ecmascript
[!info] related notes
- 所属 MOC: ECMAScript MOC, 前端基础 MOC
- 前置概念: ECMAScript Promise 与异步, JS 事件循环
- 并列概念: Promise 封装回调式异步
异步错误处理
这篇笔记回答一个问题:为什么 try/catch 抓不到异步代码中的错误,以及如何正确处理。
一句话定义
try/catch 只能捕获当前同步调用栈中的异常,异步回调执行时已经不在原来的调用栈中,所以外层 try/catch 无法捕获。
核心机制
为什么 try/catch 抓不到异步错误
try {
setTimeout(() => {
throw new Error('catch me');
}, 0);
} catch (e) {
// 不会执行到这里
console.log('caught:', e.message);
}
原因:setTimeout 回调是在未来的事件循环中执行,执行时外层 try/catch 的调用栈已经销毁了。
Promise 中的错误处理
Promise 有自己的错误处理机制,通过 .catch() 捕获。
Promise.reject(new Error('promise error'))
.catch(e => console.log('caught:', e.message));
async/await 中的错误处理
async/await 允许用 try/catch 包裹异步调用,因为 await 会暂停当前 async 函数,等 Promise settle 后恢复。
async function fetchData() {
try {
const res = await fetch('/api/data');
if (!res.ok) throw new Error('network error');
return await res.json();
} catch (e) {
console.log('caught:', e.message);
}
}
各类异步场景的错误处理
setTimeout / setInterval
// 错误做法
try {
setTimeout(() => { throw new Error('oops'); }, 0);
} catch (e) {} // 抓不到
// 正确做法:在回调内部处理
setTimeout(() => {
try {
// 可能出错的逻辑
} catch (e) {
console.error(e);
}
}, 0);
事件监听器
// 错误做法
try {
button.addEventListener('click', () => { throw new Error('click error'); });
} catch (e) {} // 抓不到
// 正确做法
button.addEventListener('click', () => {
try {
// 事件处理逻辑
} catch (e) {
console.error(e);
}
});
Promise.all 中的错误
Promise.all 只要有一个 reject 就会整体 reject。
try {
const [a, b] = await Promise.all([
fetch('/api/a'),
fetch('/api/b'),
]);
} catch (e) {
// 任意一个请求失败都会进入这里
console.error('one of the requests failed:', e);
}
多个 fetch 都完成后再继续
// 用 Promise.all 等待所有请求完成
const results = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
]);
// 如果不在意个别失败,可以用 Promise.allSettled
const settled = await Promise.allSettled([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
]);
全局错误捕获
// 未处理的 Promise rejection
window.addEventListener('unhandledrejection', e => {
console.error('unhandled rejection:', e.reason);
e.preventDefault(); // 防止控制台报错
});
// 未捕获的同步错误
window.addEventListener('error', e => {
console.error('uncaught error:', e.error);
});
常见边界
- await 只能捕获 await 表达式本身的 reject,不能捕获后续同步代码的 throw(虽然一般 try/catch 也能抓到)
- Promise 链中如果中间有 .catch() 处理了错误,后续的 .catch() 不会触发
- Node.js 中还有 process.on(‘uncaughtException’) 和 process.on(‘unhandledRejection’)
最小例子
// 错误:try/catch 抓不到异步错误
try {
setTimeout(() => { throw new Error('oops'); }, 0);
} catch (e) { /* 抓不到 */ }
// 正确:Promise + catch
fetch('/api/data')
.then(r => r.json())
.catch(e => console.error(e));
// 正确:async/await + try/catch
async function getData() {
try {
const res = await fetch('/api/data');
return await res.json();
} catch (e) {
console.error(e);
}
}