⭐ 推荐:社区规则条款 V1.0

前端错误监控体系:window.onerror 与 unhandledrejection

yipeng
yipeng 正式会员正式会员认证极客认证极客
发布于 2026-09-23 18:27 ·96 浏览 ·0 回复
内容摘要

前端错误需分三类分别监控:同步运行时错误用 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 这种行号没法定位。两个办法:

  1. 打包时开启 sourcemap,上报后到 Sentry 这类平台或自建服务还原;
  2. 引用第三方脚本时加 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 还原,堆栈才有意义
本文转载自 Clara轻量论坛系统,原文地址:https://www.leleweb.cn/thread-572.html
转载请注明出处,版权归原作者所有。
他们都看过 1 人浏览过
CLARA轻量论坛系统
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 0

还没有回复,来抢沙发~