前端错误捕获方案总结
写在前面
在前端监控 sdk 开发中,我们都会用到错误捕获,将页面各类错误进行捕获并上报日志,来获取错误信息,所以我们非常有必要深入了解下各类错误的错误捕获方式。
补充
setTimeout 错误捕获方式:https://www.cnblogs.com/beileixinqing/p/16987679.html
window.onerror 和window.addEventListener('error',fn,true)的区别 : https://www.cnblogs.com/beileixinqing/p/17013219.html
注意
- 错误捕获事件监听要写在最前面,即要保证在错误代码运行前已经监听
unhandledrejection
事件仅当 promise reject 且没有使用 catch 捕获的时候会触发- 由于网络请求异常不会事件冒泡,因此必须在捕获阶段将其捕捉到才行,于是window.addEventListener 设置为 true 在捕获阶段进行
正文
错误信息是最基础也是最重要的数据,错误信息主要分为下面几类:
- JS 代码运行错误、语法错误等
- 异步错误等
- 静态资源加载错误
- 接口请求报错
错误捕获方式
1)try/catch
只能捕获代码常规的运行错误,语法错误和异步错误不能捕获到
示例:
// 示例1:常规运行时错误,可以捕获 ✅ try { let a = undefined; if (a.length) { console.log('111'); } } catch (e) { console.log('捕获到异常:', e); } // 示例2:语法错误,不能捕获 ❌ try { const notdefined, } catch(e) { console.log('捕获不到异常:', 'Uncaught SyntaxError'); } // 示例3:异步错误,不能捕获 ❌ try { setTimeout(() => { console.log(notdefined); }, 0) } catch(e) { console.log('捕获不到异常:', 'Uncaught ReferenceError'); }
2) window.onerror
window.onerror 可以捕获常规错误、异步错误,但不能捕获资源错误
/** * @param { string } message 错误信息 * @param { string } source 发生错误的脚本URL * @param { number } lineno 发生错误的行号 * @param { number } colno 发生错误的列号 * @param { object } error Error对象 */ window.onerror = function(message, source, lineno, colno, error) { console.log('捕获到的错误信息是:', message, source, lineno, colno, error ) }
window.onerror = function(message, source, lineno, colno, error) { console.log("捕获到的错误信息是:", message, source, lineno, colno, error); }; // 示例1:常规运行时错误,可以捕获 ✅ console.log(notdefined); // 示例2:语法错误,不能捕获 ❌ const notdefined; // 示例3:异步错误,可以捕获 ✅ setTimeout(() => { console.log(notdefined); }, 0); // 示例4:资源错误,不能捕获 ❌ let script = document.createElement("script"); script.type = "text/javascript"; script.src = "https://www.test.com/index.js"; document.body.appendChild(script);
3) window.addEventListener
当静态资源加载失败时,会触发 error 事件, 此时 window.onerror 不能捕获到
示例:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> </head> <script> window.addEventListener('error', (error) => { console.log('捕获到异常:', error); }, true) </script> <!-- 图片、script、css加载错误,都能被捕获 ✅ --> <img src="https://test.cn/×××.png"> <script src="https://test.cn/×××.js"></script> <link href="https://test.cn/×××.css" rel="stylesheet" /> <script> // new Image错误,不能捕获 ❌ // new Image运用的比较少,可以自己对创建的图片使用 onerror 事件单独处理
let img = new Image();
img.src = 'https://test.cn/×××.png'
img.onerror = () => {
};
</script> </html>
4)Promise错误
Promise中抛出的错误,无法被 window.onerror、try/catch、 error 事件捕获到,可通过 unhandledrejection 事件来处理
示例:
try { new Promise((resolve, reject) => { JSON.parse(""); resolve(); }); } catch (err) { // try/catch 不能捕获Promise中错误 ❌ console.error("in try catch", err); } // error事件 不能捕获Promise中错误 ❌ window.addEventListener( "error", error => { console.log("捕获到异常:", error); }, true ); // window.onerror 不能捕获Promise中错误 ❌ window.onerror = function(message, source, lineno, colno, error) { console.log("捕获到异常:", { message, source, lineno, colno, error }); }; // unhandledrejection 可以捕获Promise中的错误 ✅ window.addEventListener("unhandledrejection", function(e) { console.log("捕获到异常", e); // preventDefault阻止传播,不会在控制台打印 e.preventDefault(); });
Vue 错误
Vue项目中,window.onerror 和 error 事件不能捕获到常规的代码错误
异常代码:
export default { created() { let a = null; if(a.length > 1) { // ... } } }; // main.js中添加捕获代码: window.addEventListener('error', (error) => { console.log('error', error); }); window.onerror = function (msg, url, line, col, error) { console.log('onerror', msg, url, line, col, error); };
控制台会报错, window.onerror 和 error 不能捕获到

vue 通过 Vue.config.errorHander
来捕获异常:
Vue.config.errorHandler = (err, vm, info) => { console.log('进来啦~', err); }
控制台打印:
errorHandler源码分析
在src/core/util
目录下,有一个error.js
文件
function globalHandleError (err, vm, info) { // 获取全局配置,判断是否设置处理函数,默认undefined // 配置config.errorHandler方法 if (config.errorHandler) { try { // 执行 errorHandler return config.errorHandler.call(null, err, vm, info) } catch (e) { // 如果开发者在errorHandler函数中,手动抛出同样错误信息throw err,判断err信息是否相等,避免log两次 if (e !== err) { logError(e, null, 'config.errorHandler') } } } // 没有配置,常规输出 logError(err, vm, info) } function logError (err, vm, info) { if (process.env.NODE_ENV !== 'production') { warn(`Error in ${info}: "${err.toString()}"`, vm) } /* istanbul ignore else */ if ((inBrowser || inWeex) && typeof console !== 'undefined') { console.error(err) } else { throw err } }
通过源码明白了,vue 使用 try/catch 来捕获常规代码的报错,被捕获的错误会通过 console.error 输出而避免应用崩溃
可以在 Vue.config.errorHandler 中将捕获的错误上报
Vue.config.errorHandler = function (err, vm, info) { // handleError方法用来处理错误并上报 handleError(err); }
React 错误
从 react16 开始,官方提供了 ErrorBoundary 错误边界的功能,被该组件包裹的子组件,render 函数报错时会触发离当前组件最近父组件的ErrorBoundary
生产环境,一旦被 ErrorBoundary 捕获的错误,也不会触发全局的 window.onerror 和 error 事件
父组件代码:
import React from 'react'; import Child from './Child.js'; // window.onerror 不能捕获render函数的错误 ❌ window.onerror = function (err, msg, c, l) { console.log('err', err, msg); }; // error 不能render函数的错误 ❌ window.addEventListener( 'error', (error) => { console.log('捕获到异常:', error); },true ); class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(error) { // 更新 state 使下一次渲染能够显示降级后的 UI return { hasError: true }; } componentDidCatch(error, errorInfo) { // componentDidCatch 可以捕获render函数的错误 console.log(error, errorInfo) // 同样可以将错误日志上报给服务器 reportError(error, errorInfo); } render() { if (this.state.hasError) { // 自定义降级后的 UI 并渲染 return <h1>Something went wrong.</h1>; } return this.props.children; } } function Parent() { return ( <div> 父组件 <ErrorBoundary> <Child /> </ErrorBoundary> </div> ); } export default Parent;
子组件代码:
// 子组件 渲染出错 function Child() { let list = {}; return ( <div> 子组件 {list.map((item, key) => ( <span key={key}>{item}</span> ))} </div> ); } export default Child;
同vue项目的处理类似,react项目中,可以在 componentDidCatch 中将捕获的错误上报
componentDidCatch(error, errorInfo) { // handleError方法用来处理错误并上报 handleError(err); }
跨域问题
如果当前页面中,引入了其他域名的JS资源,如果资源出现错误,error 事件只会监测到一个 script error
的异常。
示例:
window.addEventListener("error", error => { console.log("捕获到异常:", error); }, true ); // 当前页面加载其他域的资源,如https://www.test.com/index.js <script src="https://www.test.com/index.js"></script> // 加载的https://www.test.com/index.js的代码 function fn() { JSON.parse(""); } fn();
报错信息:

只能捕获到 script error
的原因:
是由于浏览器基于安全考虑
,故意隐藏了其它域JS文件抛出的具体错误信息,这样可以有效避免敏感信息无意中被第三方(不受控制的)脚本捕获到,因此,浏览器只允许同域下的脚本捕获具体的错误信息
解决方法:
前端script加crossorigin,后端配置 Access-Control-Allow-Origin
<script src="https://www.test.com/index.js" crossorigin></script>
添加 crossorigin 后可以捕获到完整的报错信息:

如果不能修改服务端的请求头,可以考虑通过使用 try/catch 绕过,将错误抛出
<!doctype html> <html> <body> <script src="https://www.test.com/index.js"></script> <script> window.addEventListener("error", error => { console.log("捕获到异常:", error); }, true ); try { // 调用https://www.test.com/index.js中定义的fn方法 fn(); } catch (e) { throw e; } </script> </body> </html>
接口错误
接口监控的实现原理:针对浏览器内置的 XMLHttpRequest、fetch 对象,利用 AOP 切片编程重写该方法,实现对请求的接口拦截,从而获取接口报错的情况并上报
1)拦截XMLHttpRequest请求示例:
function xhrReplace() { if (!("XMLHttpRequest" in window)) { return; } const originalXhrProto = XMLHttpRequest.prototype; // 重写XMLHttpRequest 原型上的open方法 replaceAop(originalXhrProto, "open", originalOpen => { return function(...args) { // 获取请求的信息 this._xhr = { method: typeof args[0] === "string" ? args[0].toUpperCase() : args[0], url: args[1], startTime: new Date().getTime(), type: "xhr" }; // 执行原始的open方法 originalOpen.apply(this, args); }; }); // 重写XMLHttpRequest 原型上的send方法 replaceAop(originalXhrProto, "send", originalSend => { return function(...args) { // 当请求结束时触发,无论请求成功还是失败都会触发 this.addEventListener("loadend", () => { const { responseType, response, status } = this; const endTime = new Date().getTime(); this._xhr.reqData = args[0]; this._xhr.status = status; if (["", "json", "text"].indexOf(responseType) !== -1) { this._xhr.responseText = typeof response === "object" ? JSON.stringify(response) : response; } // 获取接口的请求时长 this._xhr.elapsedTime = endTime - this._xhr.startTime; // 上报xhr接口数据 reportData(this._xhr); }); // 执行原始的send方法 originalSend.apply(this, args); }; }); } /** * 重写指定的方法 * @param { object } source 重写的对象 * @param { string } name 重写的属性 * @param { function } fn 拦截的函数 */ function replaceAop(source, name, fn) { if (source === undefined) return; if (name in source) { var original = source[name]; var wrapped = fn(original); if (typeof wrapped === "function") { source[name] = wrapped; } } }
2)拦截fetch请求示例:
function fetchReplace() { if (!("fetch" in window)) { return; } // 重写fetch方法 replaceAop(window, "fetch", originalFetch => { return function(url, config) { const sTime = new Date().getTime(); const method = (config && config.method) || "GET"; let handlerData = { type: "fetch", method, reqData: config && config.body, url }; return originalFetch.apply(window, [url, config]).then( res => { // res.clone克隆,防止被标记已消费 const tempRes = res.clone(); const eTime = new Date().getTime(); handlerData = { ...handlerData, elapsedTime: eTime - sTime, status: tempRes.status }; tempRes.text().then(data => { handlerData.responseText = data; // 上报fetch接口数据 reportData(handlerData); }); // 返回原始的结果,外部继续使用then接收 return res; }, err => { const eTime = new Date().getTime(); handlerData = { ...handlerData, elapsedTime: eTime - sTime, status: 0 }; // 上报fetch接口数据 reportData(handlerData); throw err; } ); }; }); }
错误捕获方式主要摘抄自:https://juejin.cn/post/7172072612430872584#heading-10,主要用来记录和学习,也推荐大家看看原博主的文章。
前端开发中的Error
JavaScript中的Error
JavaScript中,Error
是一个构造函数,通过它创建一个错误对象。当运行时错误产生时,Error的实例对象会被抛出。构造一个Error的语法如下:
// message: 错误描述 // fileName: 可选。被创建的Error对象的fileName属性值。默认是调用Error构造器代码所在的文件的名字。 // lineNumber: 可选。被创建的Error对象的lineNumber属性值。默认是调用Error构造器代码所在的文件的行号。 new Error([message[, fileName[, lineNumber]]])
ECMAScript标准:
Error有两个标准属性:
Error.prototype.name
:错误的名字Error.prototype.message
:错误的描述
例如,在chrome控制台中输入以下代码:
var a = new Error('错误测试'); console.log(a); // Error: 错误测试 // at <anonymous>:1:9 console.log(a.name); // Error console.log(a.message); // 错误测试
Error只有一个标准方法:
Error.prototype.toString
:返回表示一个表示错误的字符串。
接上面的代码:
a.toString(); // "Error: 错误测试"
非标准的属性
各个浏览器厂商对于Error都有自己的实现。比如下面这些属性:
Error.prototype.fileName
:产生错误的文件名。Error.prototype.lineNumber
:产生错误的行号。Error.prototype.columnNumber
:产生错误的列号。Error.prototype.stack
:堆栈信息。这个比较常用。
这些属性均不是标准属性,在生产环境中谨慎使用。不过现代浏览器差不多都支持了。
Error的种类
除了通用的Error构造函数外,JavaScript还有7个其他类型的错误构造函数。
- InternalError: 创建一个代表Javascript引擎内部错误的异常抛出的实例。 如: “递归太多”。非ECMAScript标准。
- RangeError: 数值变量或参数超出其有效范围。例子:var a = new Array(-1);
- EvalError: 与eval()相关的错误。eval()本身没有正确执行。
- ReferenceError: 引用错误。 例子:console.log(b);
- SyntaxError: 语法错误。例子:var a = ;
- TypeError: 变量或参数不属于有效范围。例子:[1,2].split(’.’)
- URIError: 给 encodeURI或 decodeURl()传递的参数无效。例子:decodeURI(’%2’)
当JavaScript运行过程中出错时,会抛出上8种(上述7种加上通用错误类型)错误中的其中一种错误。错误类型可以通过error.name拿到。
你也可以基于Error构造自己的错误类型,这里就不展开了。
其他错误
上面介绍的都是JavaScript本身运行时会发生的错误。页面中还会有其他的异常,比如错误地操作了DOM。
DOMException
DOMException是W3C DOM核心对象,表示调用一个Web Api时发生的异常。什么是Web Api呢?最常见的就是DOM元素的一系列方法,其他还有XMLHttpRequest、Fetch等等等等,这里就不一一说明了。直接看下面一个操作DOM的例子:
var node = document.querySelector('#app'); var refnode = node.nextSibling; var newnode = document.createElement('div'); node.insertBefore(newnode, refnode); // 报错:Uncaught DOMException: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.
单从JS代码逻辑层面来看,没有问题。但是代码的操作不符合DOM的规则。
DOMException
构造函数的语法如下:
// message: 可选,错误描述。 // name: 可选,错误名称。常量,具体值可以在这里找到:https://developer.mozilla.org/zh-CN/docs/Web/API/DOMException new DOMException([message[, name]]);
DOMException
有以下三个属性:
DOMException.code
:错误编号。DOMException.message
:错误描述。DOMException.name
:错误名称。
以上面那段错误代码为例,其抛出的DOMException各属性的值为:
code: 8 message: "Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node." name: "NotFoundError"
Promise
产生的异常
在Promise
中,如果Promise
被reject
了,就会抛出异常:PromiseRejectionEvent
。注意,下面两种情况都会导致Promise
被reject
:
- 业务代码本身调用了
Promise.reject
。 Promise
中的代码出错。
PromiseRejectionEvent
的构造函数目前在浏览器中大多都不兼容,这里就不说了。
PromiseRejectionEvent
的属性有两个:
PromiseRejectionEvent.promise
:被reject
的Promise
。PromiseRejectionEvent.reason
:Promise
被reject
的原因。会传递给reject
。Promsie
的catch
中的参数。
加载资源出错
由于网络,安全等原因,网页加载资源失败,请求接口出错等,也是一种常见的错误。
关于错误的小结
一个网页在运行过程中,可能发生三种错误:
- JavaScript在运行过程,语言自身抛出的异常。
- JavaScript在运行过程中,调用Web Api时发生异常。
- Promise中的拒绝。
- 网页加载资源,调用接口时发生异常。
我认为,对于前两种错误,我们在平时的开发过程中,不用特别去区分,可以统一成:【代码出错】
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南