处理反常的意义
随着网页项目越来越杂乱,许多反常报错很难在开发和测验阶段被发现,虽然你或许避开了语法等惯例过错,但不可避免的是代码在运转时的过错你依旧无法精确预料,假设现在有如下一段 Vue
代码,它在生命周期的 created
阶段异步恳求并接收了过错的数据,或许就会导致页面烘托呈现过错:
<template>
{{ test.obj.xxx }}
</template>
......
created() {
this.getSomeData()
},
methods: {
getSomeData() {
this.fetch().then((res) => {
this.test = res // 假设这是恳求的过错数据
})
},
}
而假如测验人员及时发现了这一过错的话,当他翻开控制台时往往就会立即下结论了:噢,是前端的锅
事实上真正的项目中或许会遇到更多”奇妙”的问题,而且假如过错仅产生在某些用户端,那将无从发觉,所以咱们会想到应该在程序中处理捕获运转时过错,将过错上报至服务器,然后剖析和改善代码来修复已经产生的过错。
所以该怎么应对并处理或许产生的某些过错,成为了前端开发的一门必修课,你当然能够在每个代码片段中重复编写 try...catch...
、为每个 Promise
都处理 catch
,但这难免显得有些难堪,所以我考虑能不能用更优雅的办法,一致处理一切反常,将过错在大局进行捕获然后上报剖析。其实在 Vue 中完成这样大局的反常处理并不难,下面看看我是怎么做的吧。
怎么大局捕获过错反常
查询 Vue 文档咱们能够发现大局装备中就有这么一个捕获过错的处理钩子 errorHandler,用法很简略:
Vue.config.errorHandler = function (err, vm, info) {
// `info` 是 Vue 特定的过错信息,比如过错所在的生命周期钩子
// 只在 2.2.0+ 可用
}
只需求用这个钩子就能够处理大部分 Vue 运用中的过错(如组件生命周期中的过错、自定义事情处理函数内部过错、v-on
DOM 监听器内部抛出的过错),而且回调中自带的 info
参数也标记了这个过错大概是归于哪类,一起它还能处理回来 Promise
链的过错,能够说是十分强大了,但是它也并非能处理一切的反常,不然文章写到这就该完毕了 ~ 接下来咱们测验一下。
首先在大局过错捕获中输出一下 log,先运转一下最初的恳求数据过错例子:
Vue.config.errorHandler = function (err, vm, info) {
console.log('vue反常过错捕获: ', '过错产生在 ' + info)
}
能够看到反常成功被捕获了,由于咱们模拟了一个数据过错导致烘托犯错,所以过错产生在 render
层,假如是在函数中的 Promise
产生的过错呢?咱们试一下:
async created() {
await this.getSomeData()
},
method: {
async getSomeData() {
const res = await this.fetch()
this.test = res
},
fetch() {
asdasd = 1 // 这儿给一个未定义的变量赋值,肯定会报错
return new Promise((resolve) => {
// ......省掉
})
}
没有问题,接下来咱们再试试一个按钮用 v-on
绑定 click
,但是故意在办法内制作过错,看看是什么作用:
<button @click="doSomeThing"> Test </button>
..........
doSomeThing() {
aaaaaaaa = 111111 // 这儿给一个未定义的变量赋值,肯定会报错
},
看来事情也能正常捕获,咱们再试试写一个组件,在组件中自定义一个事情,看看成果怎么:
<my-custom-comp @node-click="doSomeThing" />
// 在组件中是 $emit 触发:
this.$emit('node-click', item)
这个反常依旧是被成功捕获了,当然生命周期钩子中的过错反常也都能成功捕获,就不多做演示了,到目前为止都没有什么问题,但是假如过错不产生在 Vue 内部呢?
<button onclick="foo()">bad button</button>
能够看到这个反常没有被顺利捕获,同样的,假如是外部 JS 代码报错,也都是无法捕获的,也便是说这个钩子只能捕获与 Vue 相关联的事情。
宏使命中的过错也是无法捕获的:
.......
fetch() {
return new Promise((resolve) => {
setTimeout(() => {
asd = 1 // 在宏使命的异步中呈现的过错
resolve({})
}, 1000)
})
},
假如 Promise
反常未被正常处理的话,也是捕获不到的,如下代码,注意这儿 create
没有用 await
办法调用异步办法:
created() {
this.getSomeData();
},
methods: {
async getSomeData() {
await (asdasd = 1);
},
},
下面咱们就逐个处理这两个场景下的过错捕获问题。
处理 JS 的额定过错
咱们能够用 BOM 提供的大局过错处理函数 window.onerror
来测验捕获,它接收多个参数:
window.onerror = function (message, source, line, column, error) {
console.log('大局捕获过错', message, source, line, column, error)
}
犯错代码:
<button onclick="foo()">bad button</button>
现在 JS 反常过错都能够被捕获到了,包括 setTimeout
宏使命的异步过错也能够被捕获,但咱们注意到未被正常处理的 Promise
过错仍不能成功捕获。
处理 Promise 过错
参阅 Vue 中 error.js
的代码,同步使命反常捕获便是套上一层 try...catch...
,这也解释了为什么 Vue 捕获的过错不会被大局 window.onerror
再次捕获,由于已经在这儿抛出了。而异步使命反常处理则是判断假如是 Promise
则把 catch
指向过错处理中:
咱们能够仿照写一个插件,来处理 Vue 实例中 methods
的反常。
function isPromise(ret) {
return ret && typeof ret.then === 'function' && typeof ret.catch === 'function'
}
const handleMethods = (instance) => {
if (instance.$options.methods) {
let actions = instance.$options.methods || {}
for (const key in actions) {
if (Object.hasOwnProperty.call(actions, key)) {
let fn = actions[key]
actions[key] = function (...args) {
let ret = args.length > 0 ? fn.apply(this, args) : fn.call(this)
if (isPromise(ret) && !ret._handled) {
ret._handled = true
return ret.catch((e) => errorHandler(e, this, `捕获到了未处理的Promise反常: (Promise/async)`))
}
}
}
}
}
}
export default {
install: (Vue, options) => {
Vue.mixin({
beforeCreate() {
this.$route.meta.capture && handleMethods(this)
},
})
},
}
由于遍历一切办法或许会造成页面功用丢失,所以这儿我加了一个条件,需求在路由设置
meta
才能开启该组件下的method
额定反常捕获。
再试试上面的过错代码,看看成果:
created() {
this.getSomeData();
},
methods: {
async getSomeData() {
await (asdasd = 1);
},
},
能够被正常捕获,这种办法的好处是咱们能够把产生过错的实例信息传进去,假如不想运用这种办法,或是在 Vue3 中运用 setup
办法而不是 options
写法,还能够运用大局的事情监听来捕获:
window.addEventListener('unhandledrejection', (event) => {
console.log('大局捕获未处理的Promise反常', event)
})
完好代码
errorPlugin.js
function errorHandler(err, vm, info) {
console.log('vue反常过错捕获: ', '过错信息 ' + info)
// TODO: 处理过错上报
}
const handleMethods = (instance) => {
if (instance.$options.methods) {
let actions = instance.$options.methods || {}
for (const key in actions) {
if (Object.hasOwnProperty.call(actions, key)) {
let fn = actions[key]
actions[key] = function (...args) {
let ret = args.length > 0 ? fn.apply(this, args) : fn.call(this)
if (isPromise(ret) && !ret._handled) {
ret._handled = true
return ret.catch((e) => errorHandler(e, this, `捕获到了未处理的Promise反常: (Promise/async)`))
}
}
}
}
}
}
function isPromise(ret) {
return ret && typeof ret.then === 'function' && typeof ret.catch === 'function'
}
let GlobalError = {
install: (Vue, options) => {
Vue.config.errorHandler = errorHandler
// eslint-disable-next-line max-params
window.onerror = function (message, source, line, column, error) {
errorHandler(message, null, '大局捕获过错')
// console.log('大局捕获过错', message, source, line, column, error)
}
window.addEventListener('unhandledrejection', (event) => {
errorHandler(event, null, '大局捕获未处理的Promise反常')
})
Vue.mixin({
beforeCreate() {
this.$route.meta.capture && handleMethods(this)
},
})
},
}
export default GlobalError
main.js 中引进
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
// 引进过错处理插件
import ErrorPlugin from './errorPlugin'
Vue.use(ErrorPlugin)
new Vue({
router,
render: (h) => h(App),
}).$mount('#app')
在 Vue3 中运用
相同在 main.js 中引进插件即可:
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import ErrorPlugin from './errorPlugin'
createApp(App).use(router).use(ErrorPlugin).mount('#app')
完毕
假如你需求更加丰富的过错收集剖析功用,仍是得运用如Sentry、Bugsnag这类完善的过错追踪服务,不过相对来讲这些都需求不少装备部署操作。本文介绍了怎么简略地在 Vue 中大局捕获反常过错,提高代码健壮性,且能避免在代码中编写很多反常捕获块,一起也减少了犯错时控制台的大片飘红报警,收集过错能够协助咱们定位开发与测验阶段不易发现的疑难杂症,这部分能够运用 http
恳求将过错信息发送到服务器。
往期精彩
# 一行代码引发的 JS 探求 : call 和 apply 究竟哪个更快?
# 当UI走查说页面色值过错时,先别急着检查代码
# 运用语义化 HTML + CSS 编写一个原生 Web Components 组件
# CSS 容器查询来了,你不能错失的10个精彩事例共享!