前端错误监控体系:window.onerror 与 unhandledrejection
前端错误需分三类分别监控:同步运行时错误用 window.onerror 捕获并在第五个参数的堆栈中取栈信息,资源加载失败因不冒泡须在捕获阶段监听 error 事件并通过 e.target 的 src/href 判断,Promise 未捕获拒绝则监听 unhandledrejection 并从 e.reason 取信息,只挂 window.onerror 会漏掉 Promise 报错,且多个监听共…
学完这篇,你能亲手写出一套同时兜住同步报错、资源加载失败和 Promise 未捕获拒绝的前端错误监控上报代码。
第一步:先分清错误从哪个口子出来
前端错误大致分三类,对应两个 API:
- 同步运行时错误(
undefined is not a function这种)→window.onerror - 资源加载失败(图片、脚本、样式 404)→ 只能用捕获阶段的
error事件 - Promise 里报错但没写
.catch()→unhandledrejection
很多人只挂了 window.onerror,结果线上 Promise 报错一条都收不到,这是最常见的漏报点。
第二步:写对 window.onerror
window.onerror = function (message, source, lineno, colno, error) {
report({
type: 'error',
message: message,
stack: error && error.stack, // 关键:堆栈在第五个参数里
file: source,
line: lineno,
col: colno,
});
return false; // 返回 true 会吃掉控制台的默认报错,调试期不建议
};
注意:
window.onerror是赋值式的,页面里只有一个。如果你用的框架或第三方 SDK 也挂了它,后挂的会覆盖前面的。要共存就改成window.addEventListener('error', handler),这样能挂多个。
注意:
stack一定要从第五个参数error上取,直接用message是拿不到调用栈的。
第三步:资源加载错误要单独监听
图片、<script>、<link> 加载失败不会走 window.onerror,因为资源错误不冒泡。必须在捕获阶段截:
window.addEventListener('error', function (e) {
const target = e.target;
if (target && (target.src || target.href)) {
report({
type: 'resource',
tag: target.tagName,
url: target.src || target.href,
});
}
}, true); // 第三个参数 true 不能省
注意:这个监听器里
e.message是空的,判断是不是资源错误就看e.target有没有src/href。
第四步:补上 unhandledrejection
window.addEventListener('unhandledrejection', function (e) {
const reason = e.reason;
report({
type: 'promise',
message: reason instanceof Error ? reason.message : String(reason),
stack: reason instanceof Error ? reason.stack : '',
});
// e.preventDefault(); // 需要的话可以压掉控制台红字
});
注意:
e.reason不一定是 Error。有人Promise.reject('出错了')、Promise.reject({code: 500}),直接取.stack会得到undefined。上面这段做了归一化,别偷懒。
如果项目里用了 axios/fetch 封装,建议在请求拦截器里再兜一层,Promise 里的网络错误往往被业务代码吞掉了。
第五步:上报本身别把页面搞挂
推荐用 navigator.sendBeacon,它在页面卸载时也能发出请求:
function report(payload) {
payload.url = location.href;
payload.ts = Date.now();
payload.ua = navigator.userAgent;
try {
const data = JSON.stringify(payload);
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/log/error', data);
} else {
new Image().src = '/api/log/error?d=' + encodeURIComponent(data);
}
} catch (err) {
// 上报失败绝不能再抛错,否则死循环
}
}
注意:
sendBeacon有体积限制(约 64KB),别把整个 DOM 快照塞进去。同时要做去重和采样——同一个错误短时间刷几百条,后端会被打爆。常规做法是「错误信息 + 行号」做 key,同 key 每分钟只报一次。
第六步:让堆栈能看懂
线上代码是压缩过的,a.js:1:23456 这种行号没法定位。两个办法:
- 打包时开启 sourcemap,上报后到 Sentry 这类平台或自建服务还原;
- 引用第三方脚本时加
crossorigin="anonymous",并且 CDN 返回Access-Control-Allow-Origin,否则你只会收到一条Script error.,什么信息都没有。
注意:
Script error.基本就是跨域脚本没配 CORS,不是代码本身的问题。
另外,框架有自己的错误出口:Vue 是 app.config.errorHandler,React 是 ErrorBoundary / componentDidCatch。它们能捕获组件渲染期的错误,但不会自动进你的 window.onerror 上报,需要手动转发一次。
小结
- 同步错误用
window.onerror,堆栈从第五个参数取 - 资源错误用
addEventListener('error', fn, true),捕获阶段才拦得到 - Promise 错误用
unhandledrejection,reason要归一化 - 上报用
sendBeacon,并且整段包try/catch,绝不允许上报自己报错 - 必做去重、采样、限流,否则监控系统先崩
- 跨域脚本加
crossorigin,否则只有Script error. - 接上 sourcemap 还原,堆栈才有意义
转载请注明出处,版权归原作者所有。
正式会员
认证极客






