前端怎样全局捕获 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。
运行时错误
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 拒绝
window.addEventListener('unhandledrejection', (event) => {
report({ type: 'promise', reason: String(event.reason), stack: event.reason?.stack });
});
事件类型是 PromiseRejectionEvent,含 promise 和 reason。拒绝之后才挂上处理函数时会触发 rejectionhandled,可用于修正"未处理"的统计。调用 preventDefault() 只是取消控制台的默认输出。来自跨域脚本的拒绝不会触发该事件。
reason 不一定是 Error,可能是字符串或普通对象,上报前要做类型判断。
资源加载失败
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 等其他源时,浏览器为避免跨源信息泄露会屏蔽错误详情。解决需要双方配合:
<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过滤非本站来源,避免噪声告警。