如何优雅处理unhandledrejection避免线上事故
项目初期的技术选型
最近刚结束一个电商类的H5项目,说实话挺折腾的。项目主要用Vue3开发,涉及到支付、商品展示这些常规功能。但让我印象最深的是处理未捕获的Promise异常这个问题。
当时选择监听unhandledrejection主要是因为测试反馈说偶尔会白屏,但又复现不出来。后来发现是某些异步请求出错后没有被捕获导致的。为了能统一处理这些异常,我决定用unhandledrejection来兜底。
核心代码就这几行
实现起来其实不复杂,关键代码就下面这些:
window.addEventListener('unhandledrejection', function(event) {
console.error('未捕获的Promise异常:', event.reason);
// 防止异常继续冒泡
event.preventDefault();
// 统一错误处理逻辑
handleGlobalError(event.reason);
});
function handleGlobalError(error) {
if (error.response && error.response.status === 401) {
// 处理未登录或token过期
redirectToLogin();
} else {
// 其他错误上报到监控平台
reportErrorToServer(error);
showUserFriendlyMessage();
}
}
function redirectToLogin() {
// 跳转到登录页
window.location.href = '/login';
}
function reportErrorToServer(error) {
fetch('https://jztheme.com/api/error-report', {
method: 'POST',
body: JSON.stringify({
message: error.message,
stack: error.stack,
timestamp: Date.now()
})
});
}
function showUserFriendlyMessage() {
alert('系统出现异常,请稍后再试');
}
最大的坑:性能问题
开始没想太多,直接在监听器里加了错误上报的逻辑。结果上线后发现页面响应变慢了,特别是在网络状况不好的时候。仔细排查才发现,每次触发unhandledrejection都会发一个HTTP请求,而我们的错误监控服务器响应又比较慢。
折腾了半天才发现这个问题,最后改成用队列缓冲的方式处理:
let errorQueue = [];
let isSending = false;
function reportErrorToServer(error) {
errorQueue.push(error);
if (!isSending) processErrorQueue();
}
async function processErrorQueue() {
if (errorQueue.length === 0) return;
isSending = true;
const batch = errorQueue.splice(0, 10); // 每次最多处理10个
try {
await fetch('https://jztheme.com/api/error-report-batch', {
method: 'POST',
body: JSON.stringify(batch.map(err => ({
message: err.message,
stack: err.stack,
timestamp: Date.now()
})))
});
} catch (e) {
console.error('错误上报失败:', e);
} finally {
isSending = false;
if (errorQueue.length > 0) processErrorQueue();
}
}
这里注意我踩过好几次坑:一个是忘了处理并发问题,导致同时发起多个上报请求;另一个是没有做批量处理,单个错误都发请求太浪费资源。
谁更灵活?谁更省事?
中间还遇到个有意思的问题:有些第三方库也会抛出未捕获的Promise异常,比如某个图片懒加载库。最初我的方案是全部拦截,但这样会导致正常的功能也受到影响。
后来调整了下策略,在handleGlobalError里加了个白名单机制:
const IGNORED_ERRORS = [
'Image load failed', // 某些图片加载失败可以忽略
'LazyLoad timeout' // 懒加载超时
];
function shouldIgnoreError(error) {
return IGNORED_ERRORS.some(msg => error.message.includes(msg));
}
function handleGlobalError(error) {
if (shouldIgnoreError(error)) return;
// 原有的错误处理逻辑...
}
这个改动虽然简单,但确实让系统更健壮了。不过还是有个小问题没完全解决:有些动态加载的组件报错后,即使捕获了异常,界面还是会卡住。暂时还没找到特别好的解决方案,目前只能靠业务代码里多写try-catch来补救。
回顾与反思
总的来说,这次使用unhandledrejection的实践还算成功。最大的收获是建立了一套相对完整的异常监控体系,能及时发现生产环境的问题。特别是那个批量上报的优化,对性能提升很明显。
不过还是有几个点可以改进:
- 错误分类还不够细,后续可以加个错误级别判断
- 用户提示方式太生硬,应该做个更友好的toast提示
- 对于某些特殊场景的异常处理还不够完善
以上是我个人对这个unhandledrejection的完整讲解,有更优的实现方式欢迎评论区交流。这个技巧的拓展用法还有很多,比如结合Sentry这类专业工具使用,后续我会继续分享这类博客。

暂无评论