vue3 如何实现全局异常处理?下面本篇文章给大家介绍一下vue3进行全局异常处理的方法,希望对大家有所帮助!
在开发组件库或者插件,经常会需要进行全局异常处理,从而实现:
全局统一处理异常;为开发者提示错误信息;方案降级处理等等。那么如何实现上面功能呢?本文先简单实现一个异常处理方法,然后结合 vue3 源码中的实现详细介绍,最后总结实现异常处理的几个核心。【相关推荐:vuejs视频教程】
本文 vue3 版本为 3.0.11
一、前端常见异常对于前端来说,常见的异常比较多,比如:
js 语法异常;ajax 请求异常;静态资源加载异常;promise 异常;iframe 异常;等等最常用的比如:
1. window.onerror通过 window.onerror文档可知,当 js 运行时发生错误(包括语法错误),触发 window.onerror():
window.onerror = function(message, source, lineno, colno, error) {  console.log('捕获到异常:',{message, source, lineno, colno, error});}
函数参数:
message:错误信息(字符串)。可用于html onerror=""处理程序中的 event。source:发生错误的脚本url(字符串)lineno:发生错误的行号(数字)colno:发生错误的列号(数字)error:error对象(对象)若该函数返回true,则阻止执行默认事件处理函数。
2.  try...catch 异常处理另外,我们也经常会使用 try...catch 语句处理异常:
try {  // do something} catch (error) {  console.error(error);}
更多处理方式,可以阅读前面推荐的文章。
3. 思考大家可以思考下,自己在业务开发过程中,是否也是经常要处理这些错误情况?那么像 vue3 这样复杂的库,是否也是到处通过 try...catch来处理异常呢?接下来一起看看。
二、实现简单的全局异常处理在开发插件或库时,我们可以通过 try...catch封装一个全局异常处理方法,将需要执行的方法作为参数传入,调用方只要关心调用结果,而无需知道该全局异常处理方法内部逻辑。大致使用方法如下:
const errorhandling = (fn, args) => {  let result;  try{    result = args ? fn(...args) : fn();  } catch (error){    console.error(error)  }  return result;}
测试一下:
const f1 = () => {    console.log('[f1 running]')    throw new error('[f1 error!]')}errorhandling(f1);/* 输出: [f1 running]error: [f1 error!]    at f1 (/users/wangpingan/leo/www/node/www/a.js:14:11)    at errorhandling (/users/wangpingan/leo/www/node/www/a.js:4:39)    at object.<anonymous> (/users/wangpingan/leo/www/node/www/a.js:17:1)    at module._compile (node:internal/modules/cjs/loader:1095:14)    at object.module._extensions..js (node:internal/modules/cjs/loader:1147:10)    at module.load (node:internal/modules/cjs/loader:975:32)    at function.module._load (node:internal/modules/cjs/loader:822:12)    at function.executeuserentrypoint [as runmain] (node:internal/modules/run_main:81:12)    at node:internal/main/run_main_module:17:47*/
可以看到,当需要为方法做异常处理时,只要将该方法作为参数传入即可。但是上面示例跟实际业务开发的逻辑差得有点多,实际业务中,我们经常会遇到方法的嵌套调用,那么我们试一下:
const f1 = () => {    console.log('[f1]')    f2();}const f2 = () => {    console.log('[f2]')    f3();}const f3 = () => {    console.log('[f3]')    throw new error('[f3 error!]')}errorhandling(f1)/*  输出:  [f1 running]  [f2 running]  [f3 running]  error: [f3 error!]    at f3 (/users/wangpingan/leo/www/node/www/a.js:24:11)    at f2 (/users/wangpingan/leo/www/node/www/a.js:19:5)    at f1 (/users/wangpingan/leo/www/node/www/a.js:14:5)    at errorhandling (/users/wangpingan/leo/www/node/www/a.js:4:39)    at object.<anonymous> (/users/wangpingan/leo/www/node/www/a.js:27:1)    at module._compile (node:internal/modules/cjs/loader:1095:14)    at object.module._extensions..js (node:internal/modules/cjs/loader:1147:10)    at module.load (node:internal/modules/cjs/loader:975:32)    at function.module._load (node:internal/modules/cjs/loader:822:12)    at function.executeuserentrypoint [as runmain] (node:internal/modules/run_main:81:12)*/
这样也是没问题的。那么接下来就是在 errorhandling方法的 catch分支实现对应异常处理即可。接下来看看 vue3 源码中是如何处理的?
三、vue3 如何实现异常处理理解完上面示例,接下来看看在 vue3 源码中是如何实现异常处理的,其实现起来也是很简单。
1. 实现异常处理方法在 errorhandling.ts 文件中定义了 callwitherrorhandling和 callwithasyncerrorhandling两个处理全局异常的方法。顾名思义,这两个方法分别处理:
callwitherrorhandling:处理同步方法的异常;callwithasyncerrorhandling:处理异步方法的异常。使用方式如下:
callwithasyncerrorhandling(  handler,  instance,  errorcodes.component_event_handler,  args)
代码实现大致如下:
// packages/runtime-core/src/errorhandling.ts// 处理同步方法的异常export function callwitherrorhandling(  fn: function,  instance: componentinternalinstance | null,  type: errortypes,  args?: unknown[]) {  let res  try {    res = args ? fn(...args) : fn(); // 调用原方法  } catch (err) {    handleerror(err, instance, type)  }  return res}// 处理异步方法的异常export function callwithasyncerrorhandling(  fn: function | function[],  instance: componentinternalinstance | null,  type: errortypes,  args?: unknown[]): any[] {  // 省略其他代码  const res = callwitherrorhandling(fn, instance, type, args)  if (res && ispromise(res)) {    res.catch(err => {      handleerror(err, instance, type)    })  }  // 省略其他代码}
callwitherrorhandling方法处理的逻辑比较简单,通过简单的 try...catch 做一层封装。而 callwithasyncerrorhandling 方法就比较巧妙,通过将需要执行的方法传入 callwitherrorhandling方法处理,并将其结果通过 .catch方法进行处理。
2. 处理异常在上面代码中,遇到报错的情况,都会通过 handleerror()处理异常。其实现大致如下:
// packages/runtime-core/src/errorhandling.ts// 异常处理方法export function handleerror(  err: unknown,  instance: componentinternalinstance | null,  type: errortypes,  throwindev = true) {  // 省略其他代码  logerror(err, type, contextvnode, throwindev)}function logerror(  err: unknown,  type: errortypes,  contextvnode: vnode | null,  throwindev = true) {  // 省略其他代码  console.error(err)}
保留核心处理逻辑之后,可以看到这边处理也是相当简单,直接通过 console.error(err)输出错误内容。
3. 配置 errorhandler 自定义异常处理函数在使用 vue3 时,也支持指定自定义异常处理函数,来处理组件渲染函数和侦听器执行期间抛出的未捕获错误。这个处理函数被调用时,可获取错误信息和相应的应用实例。文档参考:《errorhandler》使用方法如下,在项目 main.js文件中配置:
// src/main.jsapp.config.errorhandler = (err, vm, info) => {  // 处理错误  // `info` 是 vue 特定的错误信息,比如错误所在的生命周期钩子}
那么 errorhandler()是何时执行的呢?我们继续看看源码中 handleerror() 的内容,可以发现:
// packages/runtime-core/src/errorhandling.tsexport function handleerror(  err: unknown,  instance: componentinternalinstance | null,  type: errortypes,  throwindev = true) {  const contextvnode = instance ? instance.vnode : null  if (instance) {    // 省略其他代码    // 读取 errorhandler 配置项    const apperrorhandler = instance.appcontext.config.errorhandler    if (apperrorhandler) {      callwitherrorhandling(        apperrorhandler,        null,        errorcodes.app_error_handler,        [err, exposedinstance, errorinfo]      )      return    }  }  logerror(err, type, contextvnode, throwindev)}
通过 instance.appcontext.config.errorhandler取到全局配置的自定义错误处理函数,存在时则执行,当然,这边也是通过前面定义的 callwitherrorhandling来调用。
4. 调用 errorcaptured 生命周期钩子在使用 vue3 的时候,也可以通过 errorcaptured生命周期钩子来捕获来自后代组件的错误。文档参考:《errorcaptured》入参如下:
(err: error, instance: component, info: string) => ?boolean
此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false以阻止该错误继续向上传播。有兴趣的同学可以通过文档,查看具体的错误传播规则。使用方法如下,父组件监听 onerrorcaptured生命周期(示例代码使用 vue3 setup 语法):
<template>  <message></message></template><script setup>// app.vue  import { onerrorcaptured } from 'vue';  import message from './components/message.vue'  onerrorcaptured(function(err, instance, info){  console.log('[errorcaptured]', err, instance, info)})</script>
子组件如下:
<template>  <button @click="sendmessage">发送消息</button></template><script setup>// message.vueconst sendmessage = () => {  throw new error('[test onerrorcaptured]')}</script>
当点击「发送消息」按钮,控制台便输出错误:
[errorcaptured] error: [test onerrorcaptured]    at proxy.sendmessage (message.vue:36:15)    at _createelementvnode.onclick._cache.<computed>._cache.<computed> (message.vue:3:39)    at callwitherrorhandling (runtime-core.esm-bundler.js:6706:22)    at callwithasyncerrorhandling (runtime-core.esm-bundler.js:6715:21)    at htmlbuttonelement.invoker (runtime-dom.esm-bundler.js:350:13) proxy {sendmessage: ƒ, …} native event handler
可以看到 onerrorcaptured生命周期钩子正常执行,并输出子组件 message.vue内的异常。
那么这个又是如何实现呢?还是看 errorhandling.ts 中的 handleerror() 方法:
// packages/runtime-core/src/errorhandling.tsexport function handleerror(  err: unknown,  instance: componentinternalinstance | null,  type: errortypes,  throwindev = true) {  const contextvnode = instance ? instance.vnode : null  if (instance) {    let cur = instance.parent    // the exposed instance is the render proxy to keep it consistent with 2.x    const exposedinstance = instance.proxy    // in production the hook receives only the error code    const errorinfo = __dev__ ? errortypestrings[type] : type    while (cur) {      const errorcapturedhooks = cur.ec // ①取出组件配置的 errorcaptured 生命周期方法      if (errorcapturedhooks) {        // ②循环执行 errorcaptured 中的每个 hook        for (let i = 0; i < errorcapturedhooks.length; i++) {          if (            errorcapturedhooks[i](err, exposedinstance, errorinfo) === false          ) {            return          }        }      }      cur = cur.parent    }    // 省略其他代码  }  logerror(err, type, contextvnode, throwindev)}
这边会先获取 instance.parent作为当前处理的组件实例进行递归,每次将取出组件配置的 errorcaptured 生命周期方法的数组并循环调用其每一个钩子,然后再取出当前组件的父组件作为参数,最后继续递归调用下去。
5. 实现错误码和错误消息vue3 还为异常定义了错误码和错误信息,在不同的错误情况有不同的错误码和错误信息,让我们能很方便定位到发生异常的地方。错误码和错误信息如下:
// packages/runtime-core/src/errorhandling.tsexport const enum errorcodes {  setup_function,  render_function,  watch_getter,  watch_callback,  // ... 省略其他}export const errortypestrings: record<number | string, string> = {  // 省略其他  [lifecyclehooks.render_tracked]: 'rendertracked hook',  [lifecyclehooks.render_triggered]: 'rendertriggered hook',  [errorcodes.setup_function]: 'setup function',  [errorcodes.render_function]: 'render function',  // 省略其他  [errorcodes.scheduler]:    'scheduler flush. this is likely a vue internals bug. ' +    'please open an issue at https://new-issue.vuejs.org/?repo=vuejs/vue-next'}
当不同错误情况,根据错误码 errorcodes来获取 errortypestrings错误信息进行提示:
// packages/runtime-core/src/errorhandling.tsfunction logerror(  err: unknown,  type: errortypes,  contextvnode: vnode | null,  throwindev = true) {  if (__dev__) {    const info = errortypestrings[type]    warn(`unhandled error${info ? ` during execution of ${info}` : ``}`)    // 省略其他  } else {    console.error(err)  }}
6. 实现 tree shaking关于 vue3 实现 tree shaking 的介绍,可以看我之前写的高效实现框架和 js 库瘦身。其中,logerror 方法中就使用到了:
// packages/runtime-core/src/errorhandling.tsfunction logerror(  err: unknown,  type: errortypes,  contextvnode: vnode | null,  throwindev = true) {  if (__dev__) {    // 省略其他  } else {    console.error(err)  }}
当编译成 production 环境后,__dev__分支的代码不会被打包进去,从而优化包的体积。
四、总结到上面一部分,我们就差不多搞清楚 vue3 中全局异常处理的核心逻辑了。我们在开发自己的错误处理方法时,也可以考虑这几个核心点:
支持同步和异步的异常处理;
设置业务错误码、业务错误信息;
支持自定义错误处理方法;
支持开发环境错误提示;
支持 tree shaking。
原文地址:https://juejin.cn/post/7071982812668100616
(学习视频分享:vuejs教程、web前端)
以上就是一文详解vue3怎么进行全局异常处理的详细内容。
   
 
   