本文详解前端错误监控与上报机制。涵盖try-catch、window.onerror、unhandledrejection等核心捕获手段,对比不同场景下的适用性。深入解析React错误边界与Vue全局错误处理钩子,并探讨Ajax、Image及sendBeacon三种上报方式的优劣,提供完整的错误监控解决方案。
前端错误监控与捕获机制
在前端开发中,有效捕获错误是保障应用稳定性的关键。
try...catch语句用于捕获同步执行代码中抛出的异常。其生效前提是线程必须已进入try...catch块,且异常在try...catch执行期间抛出。
语法错误会在代码解析阶段的语法检查时直接报错,此时线程尚未进入try...catch代码块,因此try...catch无法捕获语法错误。
try {
a.
}catch(e) {
console.log('catch error:', e)
}
try...catch无法直接捕获setTimeout或Promise中的异步错误。若需捕获此类错误,必须将try...catch逻辑置于异步代码内部执行。
try {
new Promise((res, rej) => {
rej('promise reject error')
// throw new Error('promise throw error')
})
} catch (e) {
console.log('catch error:', e)
}
try {
setTimeout(() => {
throw new Error('setTimeout throw error')
}, 0)
} catch (e) {
console.log('catch error:', e)
}
try...catch可以捕获async函数中await关键字引发的异步异常。
async function fn() {
try {
let res = await new Promise((res, rej) => {
// rej('my reject err') // unhandledrejection 可以处理
throw Error('my throw error') // unhandledrejection 可以处理
})
} catch (err) {
console.log('catch err', err)
}
}
fn()
window.onerror事件在资源加载失败或不可用时触发,对应error事件。该方式无法捕获Promise错误,但可以捕获setTimeout中的错误。
当加载跨域脚本发生语法错误时,出于安全考虑,浏览器控制台不会显示具体错误细节,仅返回“Script error.”。解决方法是为script标签添加crossOrigin属性,并在服务端配置Access-Control-Alow-Origin:*。
当Promise处于rejected状态且未绑定reject处理器时,会触发unhandledrejection事件。
针对不同业务场景,需选择合适的错误处理策略。
综合来看,结合addEventListener('error')与addEventListener('unhandledrejection')可以覆盖五种主要异常类型:同步任务、普通异步任务、Promise任务、async任务以及资源加载错误。
一种可行的策略是将unhandledrejection捕获到的错误通过throw抛出,使其被error事件统一捕获,从而实现集中上报。
- 针对跨域资源加载问题,需监听window.addEventListener('error'),同时为script标签添加crossOrigin属性,并确保服务端配置
Access-Control-Alow-Origin。 - 对于定时器内部函数抛出的错误,可通过window.onerror或window.addEventListener('error')进行捕获。
- 静态资源加载异常可通过window.addEventListener('error')捕获,但window.onerror无法捕获此类错误。
- 网络请求异常通常通过axios的响应拦截器进行处理。
- 线上环境若使用压缩代码,需开启sourceMap以便还原错误堆栈。
对于Promise,通常配置catch处理器处理拒绝状态;未处理的rejected Promise则通过unhandledrejection事件捕获。
// 能触发 unhandledrejection ,因为未显式处理reason
Promise.reject('error').then()
Promise.reject('error').then(console.log)
// 不能触发 unhandledrejection ,因为已处理reason
Promise.reject('error').then(console.log, console.log)
// 不能触发 unhandledrejection ,因为没处理reason,直接抛出异常
Promise.reject('error')
在React中,可使用错误边界(Error Boundaries)来捕获组件树中的JavaScript错误。
错误边界是一种特殊的React组件,能够捕获其子组件树中任何位置发生的JavaScript错误。它负责打印错误信息并展示降级UI,而不会渲染发生崩溃的子组件树。错误边界可捕获渲染期间、生命周期方法及构造函数中发生的错误。
然而,错误边界无法捕获以下场景产生的错误:
- 事件处理函数中抛出的错误。
- 异步代码,例如
setTimeout或requestAnimationFrame回调函数中抛出的错误。 - 服务端渲染(SSR)过程中产生的错误。
- 错误边界自身抛出的错误(非其子组件抛出)。
在Vue框架中,可通过全局配置或生命周期钩子处理错误。
通过Vue.config.errorHandler指定全局错误处理函数。该函数在组件渲染和观察期间发生未捕获错误时被调用,可获取错误信息和Vue实例。
- 自2.2.0版本起,该钩子也捕获组件生命周期钩子中的错误。当该钩子为
undefined时,被捕获的错误会通过console.error输出,从而避免应用崩溃。 - 自2.4.0版本起,该钩子还捕获Vue自定义事件处理函数内部的错误。
- 自2.6.0版本起,该钩子捕获
v-onDOM监听器内部抛出的错误。此外,若被覆盖的钩子或处理函数返回Promise链(如async函数),其Promise链中的错误也会被处理。 - Sentry和Bugsnag等错误追踪服务均通过此选项提供官方支持。
生命周期钩子errorCaptured在捕获到后代组件错误时被调用。该钩子接收三个参数:错误对象、发生错误的组件实例以及包含错误来源信息的字符串。返回false可阻止错误向上传播。
错误传播遵循以下规则:
- 默认情况下,若定义了全局的
config.errorHandler,所有错误仍会发送给该处理器,确保错误能统一上报至分析服务。 - 若组件的继承链或父链中存在多个
errorCaptured钩子,它们将按顺序被相同的错误逐个调用。 - 若
errorCaptured钩子自身抛出错误,则新错误和原被捕获的错误都会发送给全局的config.errorHandler。 errorCaptured钩子可返回false以阻止错误继续向上传播,表明“该错误已处理并应被忽略”。这将阻止其他errorCaptured钩子和全局config.errorHandler被该错误调用。
错误信息上报是前端监控的重要环节。
捕获到错误后,需将其上报至服务器,以便进行分析和修复。
常见的上报方式主要有三种。
Ajax上报
在发现错误时,通过Ajax请求将错误数据发送至接口进行存储。
然而,Ajax上报存在以下问题:
- 受到严格的跨域限制。
- 上报请求可能会阻塞业务逻辑执行。
- 请求容易丢失,可能被浏览器强制取消。
Image上报
利用Image标签天然支持跨域且兼容所有浏览器的特性,通过创建图片对象并设置src属性来上报数据,如所示。
let img = new Image()
img.src='请求的url'
由于Image上报本质是GET请求,不同浏览器对URL长度限制不一(2kb-8kb),可能导致数据截断无法完整上报,因此该方法并非绝对安全。
sendBeacon上报
sendBeacon方法
navigator.sendBeacon()方法可用于通过HTTP POST异步传输少量数据至Web服务器。
该方法主要用于发送统计数据,同时避免了传统技术带来的问题。
此方法旨在满足统计和诊断代码在文档卸载(unload)前发送数据的需求。过早发送可能导致数据丢失,而保证在文档卸载期间发送数据一直较为困难,因为用户代理通常会忽略unload事件处理器中产生的异步XMLHttpRequest。
navigator.sendBeacon(url, data);
使用sendBeacon()方法可使浏览器在有机会时异步向服务器发送数据,同时不延迟页面卸载或影响下一导航的载入性能。具体特点包括:
- 数据发送可靠。
- 数据异步传输。
- 不影响下一导航的载入。
以上为JS前端错误监控捕获及上报方法的详细解析,更多相关内容请关注本站其他文章。