如何替代window.onunhandledrejection实现跨浏览器Promise错误捕获与日志上报?
处理Promise错误并构建客户端错误日志器的最佳实践
首先,咱们先解决你最关心的Promise错误捕获问题,再帮你搭建一个健壮的独立错误日志器:
一、Promise链内的错误处理最佳方式
对于你自己编写的Promise代码,最可靠的做法是在每一条Promise链的末尾添加.catch(),这样能精准捕获链中任何环节抛出的错误,包括.then()回调里的异常:
// 示例:带错误处理的Promise链 fetch('/api/data') .then(response => { if (!response.ok) throw new Error(`HTTP错误:${response.status}`); return response.json(); }) .then(data => { // 处理数据,这里的错误会被下面的catch捕获 processData(data); }) .catch(error => { // 统一处理链内所有错误 logger.reportError('promise_chain_error', error); });
这种方式比依赖全局事件更可控,能针对不同业务场景做定制化处理,同时避免全局事件的兼容性问题。
二、全局兜底:兼容所有未捕获的Promise拒绝
如果要捕获那些遗漏的、第三方库抛出的未处理Promise错误,window.onunhandledrejection是标准方案,但你当前的代码存在兼容性问题——e.detail.reason是旧版Chrome的非标准实现,标准属性是e.reason。我们需要做兼容处理:
window.addEventListener('unhandledrejection', (event) => { // 阻止浏览器默认的错误提示(可选) event.preventDefault(); // 兼容标准和非标准实现 const error = event.reason || (event.detail && event.detail.reason); if (error) { logger.reportError('unhandled_promise_rejection', error); } });
三、构建独立的logger.js自动捕获所有错误
除了Promise错误,客户端还需要捕获同步脚本错误,我们把所有逻辑封装到独立文件中,实现全量错误自动上报:
// logger.js class ErrorLogger { constructor(options = {}) { this.reportUrl = options.reportUrl || '/api/error-report'; // 初始化全局错误监听 this.initGlobalListeners(); } // 封装错误上报逻辑 async reportError(type, error) { try { // 收集错误详情:类型、消息、堆栈、当前URL、用户代理等 const errorDetails = { type, message: error.message || '未知错误', stack: error.stack || '无堆栈信息', url: window.location.href, userAgent: navigator.userAgent, timestamp: new Date().toISOString() }; // 上报到服务器(这里用fetch,也可以用XMLHttpRequest兼容旧浏览器) await fetch(this.reportUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(errorDetails) }); } catch (reportError) { // 上报失败的话,可以存到localStorage,下次再尝试 console.warn('错误上报失败:', reportError); this.saveToLocalStorage(type, error); } } // 初始化全局错误监听 initGlobalListeners() { // 捕获同步脚本错误 window.addEventListener('error', (event) => { event.preventDefault(); this.reportError('sync_script_error', event.error); }); // 捕获未处理的Promise拒绝 window.addEventListener('unhandledrejection', (event) => { event.preventDefault(); const error = event.reason || (event.detail && event.detail.reason); if (error) { this.reportError('unhandled_promise_rejection', error); } }); } // 上报失败时缓存到localStorage saveToLocalStorage(type, error) { const cachedErrors = JSON.parse(localStorage.getItem('cached_errors')) || []; cachedErrors.push({ type, message: error.message, stack: error.stack, timestamp: new Date().toISOString() }); localStorage.setItem('cached_errors', JSON.stringify(cachedErrors)); } // 可选:手动触发上报缓存的错误 async reportCachedErrors() { const cachedErrors = JSON.parse(localStorage.getItem('cached_errors')) || []; if (cachedErrors.length === 0) return; try { await fetch(this.reportUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ batch: cachedErrors }) }); localStorage.removeItem('cached_errors'); } catch (err) { console.warn('缓存错误上报失败:', err); } } } // 导出单例,方便全局使用 const logger = new ErrorLogger({ reportUrl: '/your-server-error-endpoint' }); // 页面加载完成后尝试上报缓存的错误 window.addEventListener('load', () => { logger.reportCachedErrors(); }); export default logger;
四、使用方式
在你的项目入口文件(比如main.js)中引入:
import logger from './logger.js'; // 之后所有未捕获的同步错误、Promise拒绝都会自动上报 // 也可以手动上报自定义错误 try { // 某些可能出错的逻辑 } catch (err) { logger.reportError('custom_error', err); }
关键注意点
- 避免过度依赖全局事件:全局事件是最后一道防线,尽量在Promise链内用
.catch()处理错误,这样能更精准定位问题。 - 兼容性:
unhandledrejection事件在IE11及以下不支持,如果需要兼容,可以考虑用Promise polyfill(比如es6-promise)并结合自定义的错误捕获逻辑。 - 堆栈信息优化:生产环境下代码可能被压缩,堆栈信息会变得模糊,建议在打包时保留sourcemap,方便服务器端解析真实代码位置。
内容的提问来源于stack exchange,提问作者Maximus Dredoff
相关产品推荐
相关产品推荐

