Vue中的errorHandler异常捕获问题
作者:初升晨光
Vue errorHandler异常捕获
异常捕获介绍
1.在日常前端开发中对于异常监控的方式可以采用 window.onerror 方式进行监听
window.onerror = function(message, source, lineno, colno, error) { // message:错误信息(字符串) // source:发生错误的脚本URL // lineno:发生错误的行号 // colno:发生错误的列号 // error:Error对象 } //或者 window.addEventListener('error', function(e) { console.log(e) console.log(e.target) })
2.在vue中需要使用errorHandler方法
onerror方法无法捕获Vue组件信息
Vue.config.errorHandler = function (err, vm, info) { // err: 具体错误信息 // vm: 当前错误所在的Vue实例 // info: 错误所在的生命周期钩子 }
errorHandler实际应用
针对项目中错误的js语法和接口请求报错进行捕获,需要注意的是接口捕获需要手动捕获
1.在main.js中注册
//因为接口报错需要手动捕获 创建公用方法 const errorHandler = (err, vm, info) => { if(err.isAxiosError){ //axios请求错误 }else{ //js语法错误 console.log('err:'err.toString()) } } //调用 Vue.config.errorHandler = errorHandler //axios手动捕获使用 绑定 prototype Vue.prototype.$throw = (error) => errorHandler(error, this)
2.axios错误捕获
在封装好的axios请求中进行错误响应捕获,将错误信息交给 errorHadaler 函数进行处理
Vue.$throw(error) //或 Vue.prototype.$throw(error)
对于error信息的解析
1.通过Json.stringify()对err进行序列化
const errorHandler = (err, vm, info) => { if(err.isAxiosError){ //axios请求错误为手动捕获 不需要进行解析处理 }else{ const errJson = JSON.stringify(err, Object.getOwnPropertyNames(err), 2) console.log(JSON.parse(errJson )) } }
2.通过error-stack-parser解析error堆栈
安装方式:
npm install error-stack-parser yarn add error-stack-parser
//引入error-stack-parser import ErrorStackParser from 'error-stack-parser' const errorHandler = (err, vm, info) => { if(err.isAxiosError){ //axios请求错误为手动捕获 不需要进行解析处理 }else{ const errJson = ErrorStackParser.parse(err)[0] console.log(errJson) //通过fileName截取页面名称 const fileName = stackInfo.fileName.match(/src.*?.vue/g)[0] console.log(fileName) } }
vue.config.errorHandler 错误处理调研
在 vue2.6.x 及之后版本中(仅限于此API未被毁灭性更新前)使用全局 errorHandler 钩子来进行 vue 组件中所抛错误的捕捉与处理。
现状
vue2.6 之前,errorHandler 只能捕捉同步函数抛出的错误,而在实际开发中我们关心得更多的是调用接口时可能抛出的错误,ES7之后通常使用 async await 的方式进行接口调用,对于async函数中抛出的错误errorHandler并不能捕捉到。
因此我们使用了装饰器(Decorator)进行错误捕捉,方法可行,痒点在于需要在很多个组件中引入装饰器,然后放置在每一个需要的位置(还有个近乎绝症的“痛点”是 vetur 插件对“不规范使用decorator”的红色警告)。
当前使用示例(尽可能简化版)
// errorConfigs.js import { debounce } from 'lodash'; const DEBOUNCE_TIME = 500; const errorProcessorConfigs = [{ assert (error) { return ErrorAssert.isUserInfoNotExist(error); // ErrorAssert为内部Error断言库,使用了webpack的ProvidePlugin声明 }, processor: debounce(() => alert('用户信息不存在'), DEBOUNCE_TIME) }, { assert (error) { return ErrorAssert.isAppInfoNotExist(error); }, processor: debounce(() => alert('应用信息不存在'), DEBOUNCE_TIME) }] // errorProcessor.js import errorProcessorConfigs from '@src/configs/errorConfigs'; function errorProcessor(error) { let errorProcessorConfig = errorProcessorConfigs.find(config => config.assert(error)); if (errorProcessorConfig !== undefined) { errorProcessorConfig.processor(error); } throw error; // 处理完之后继续将错误抛出以中断程序流程 }; export default errorProcessor; // errorCatcher.js import errorProcessor from './errorProcessor'; function errorCatcher(target, name, descriptor) { const originFunc = descriptor.value; descriptor.value = async function () { try { return await originFunc.apply(this, arguments); } catch (error) { return errorProcessor(error); } }; return descriptor; }; export default errorCatcher; // xxx.vue /* template */ <script> import errorCatcher from '@services/errorCatcher'; import checkLogin from '@services/checkLogin'; import resources from '@services/resources'; export default { @errorCatcher async created () { await checkLogin(); this.fetchUserInfo(); }, methods: { @errorCatcher async fetchUserInfo () { const userInfo = await resources.user.fetch(); // ... }, } }; </script> /* style */
console:
应用
// main.js import Vue from 'vue'; import errorProcessor from '@services/errorProcessor'; Vue.config.errorHandler = errorProcessor; /* ... */ // xxx.vue /* template */ <script> import checkLogin from '@services/checkLogin'; import resources from '@services/resources'; export default { async created () { await checkLogin(); this.fetchUserInfo(); }, methods: { async fetchUserInfo () { const userInfo = await resources.user.fetch(); // ... }, } }; </script> /* style */
对比
装饰器 | errorHandler | |
---|---|---|
优势 | 1.使用灵活,可多个装饰器组装使用;2.装饰器可以接收参数,进行更高阶的应用; | 1.全局设置,一处设置处处受益,使用较为简洁;2. 可以直接拿到 this,获取更多信息,进行更多操作;3. info 可辅助定位错误源; |
劣势 | 1.在每一个需要的函数处都需要使用装饰器进行包装,组件中也需要引入装饰器模块;2.装饰器被babel编译后细微的增加文件体积;3.编译期生效,因此如果 errorProcessorConfigs想引入 router 就需要进行一些特殊处理(如将routes 配置放在 main.js 中添加,或者通过变量方式传入) | 1.无法向错误处理中传入参数;2.全局只能有一个,如果想针对不同场景下抛出的同一类 error 进行不同处理,则需要加入判断逻辑,影响函数纯粹性。3.捕捉错误条件限制于同步函数或函数返回promise链。4.watch中调用的异步函数暂时无法被捕捉 |
心得
装饰器错误处理方式略显繁重,但并不会被errorHandler完全取缔,如果想让errorHandler尽可能的捕捉到出现的错误,则可能需要对代码进行一些更为严格的调整(构建promise链),可以根据实际的场景,将二者结合使用,以产生更大的受益(举个例子:某些场景下的错误可以使用装饰器处理并吞掉,不走全局处理)。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。