前端知库

前端怎样全局捕获 JS 错误、Promise 拒绝和资源加载失败?

window.onerror 与 error 事件捕获运行时异常,unhandledrejection 捕获未处理的 Promise 拒绝,资源加载失败要在捕获阶段监听;跨域脚本的报错只剩 Script error.,需 crossorigin 加 CORS 响应头才能拿到详情。

参考答案

简答

  • 运行时脚本错误会以 ErrorEvent 派发到 window,可用 window.onerror 或 addEventListener('error') 捕获。
  • 未处理的 Promise 拒绝不触发 error,而是触发 unhandledrejection,原因在 event.reason 中。
  • <img>、<script>、<link> 加载失败的 error 事件派发在元素上且不冒泡,全局捕获必须在 window 上使用捕获阶段监听。
  • 跨域脚本抛错时浏览器只给出 Script error.,行列号为 0、error 为 null;要拿到详情,脚本标签需加 crossorigin,服务端需返回 Access-Control-Allow-Origin。

运行时错误

JavaScript
window.onerror = (message, source, lineno, colno, error) => {
  report({ type: 'js', message, source, lineno, colno, stack: error?.stack });
};

onerror 是唯一接收多个参数的事件处理属性;addEventListener('error', handler) 则只收到一个 ErrorEvent,字段为 message、filename、lineno、colno、error。onerror 返回 true 会取消默认的控制台报错,但抛错的脚本依然中止。

try/catch 包住的错误、框架自身的错误边界吞掉的错误不会到达全局事件,需要在框架层(如 React 错误边界、Vue 的 app.config.errorHandler)另行上报。

Promise 拒绝

JavaScript
window.addEventListener('unhandledrejection', (event) => {
  report({ type: 'promise', reason: String(event.reason), stack: event.reason?.stack });
});

事件类型是 PromiseRejectionEvent,含 promise 和 reason。拒绝之后才挂上处理函数时会触发 rejectionhandled,可用于修正"未处理"的统计。调用 preventDefault() 只是取消控制台的默认输出。来自跨域脚本的拒绝不会触发该事件。

reason 不一定是 Error,可能是字符串或普通对象,上报前要做类型判断。

资源加载失败

JavaScript
window.addEventListener(
  'error',
  (event) => {
    if (event instanceof ErrorEvent) return; // 运行时错误交给上面的逻辑
    const el = event.target;
    report({ type: 'resource', tag: el.tagName, url: el.src || el.href });
  },
  true, // 资源错误不冒泡,只能在捕获阶段拿到
);

资源错误是普通 Event,拿不到 HTTP 状态码。需要状态码或耗时时,可结合 Resource Timing 数据分析。fetch 失败不属于资源错误,它表现为 Promise 拒绝,应在请求封装层统一处理。

跨域 Script error.

脚本放在 CDN 等其他源时,浏览器为避免跨源信息泄露会屏蔽错误详情。解决需要双方配合:

HTML
<script src="https://cdn.example.com/app.js" crossorigin="anonymous"></script>

CDN 响应头返回 Access-Control-Allow-Origin: https://www.example.com(或 *,但不能与 use-credentials 同用)。只加属性而服务端不返回该响应头时,脚本会整体加载失败,比拿不到详情更糟。CORS 的原理见《什么是跨域?CORS 为什么需要服务端配合?》。

常见追问

  • 为什么同一个错误被报了两次?onerror 和 addEventListener('error') 同时注册,或框架错误处理与全局监听都上报了;按错误指纹去重。
  • 压缩后的堆栈怎么看?上报原始堆栈,在服务端用 Source Map 还原,见《线上报错怎样用 Source Map 还原,又不把 map 文件公开?》。
  • 浏览器扩展注入的脚本报错怎么办?按 filename 过滤非本站来源,避免噪声告警。