百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

Vue项目处理错误上报如此简单(vue创建项目出错)

ztj100 2025-03-30 00:20 12 浏览 0 评论

处理异常的意义

随着网页项目越来越复杂,许多异常报错很难在开发和测试阶段被发现,尽管你可能避开了语法等常规错误,但不可避免的是代码在运行时的错误你仍旧无法准确预料,假设现在有如下一段 Vue 代码,它在生命周期的 created 阶段异步请求并接收了错误的数据,可能就会导致页面渲染出现错误:


......
created() {
    this.getSomeData()
},
methods: {
    getSomeData() {
      this.fetch().then((res) => {
        this.test = res // 假设这是请求的错误数据
      })
    },
}

而如果测试人员及时发现了这一错误的话,当他打开控制台时往往就会立即下结论了:噢,是前端的锅

image.png

事实上真正的项目中可能会遇到更多"奇妙"的问题,而且如果错误仅发生在某些用户端,那将无从察觉,于是我们会想到应该在程序中处理捕获运行时错误,将错误上报至服务器,然后分析和改进代码来修复已经发生的错误。

所以该如何应对并处理可能发生的某些错误,成为了前端开发的一门必修课,你当然可以在每个代码片段中重复编写 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)
}

image.png

可以看到异常成功被捕获了,因为我们模拟了一个数据错误导致渲染出错,所以错误发生在 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) => {
          // ......省略
      })
    }

image.png

没有问题,接下来我们再试试一个按钮用 v-on 绑定 click,但是故意在方法内制造错误,看看是什么效果:


..........
doSomeThing() {
  aaaaaaaa = 111111 // 这里给一个未定义的变量赋值,肯定会报错
},

image.png

看来事件也能正常捕获,我们再试试写一个组件,在组件中自定义一个事件,看看结果如何:



// 在组件中是 $emit 触发:
this.$emit('node-click', item)

image.png

这个异常依旧是被成功捕获了,当然生命周期钩子中的错误异常也都能成功捕获,就不多做演示了,到目前为止都没有什么问题,但是如果错误不发生在 Vue 内部呢?

image.png

可以看到这个异常没有被顺利捕获,同样的,如果是外部 JS 代码报错,也都是无法捕获的,也就是说这个钩子只能捕获与 Vue 相关联的事件。

宏任务中的错误也是无法捕获的:

.......
fetch() {
  return new Promise((resolve) => {
    setTimeout(() => {
      asd = 1 // 在宏任务的异步中出现的错误
      resolve({})
    }, 1000)
  })
},

image.png

如果 Promise 异常未被正常处理的话,也是捕获不到的,如下代码,注意这里 create 没有用 await 方式调用异步方法:

created() {
    this.getSomeData();
},
methods: {
    async getSomeData() {
      await (asdasd = 1);
    },
},

image.png

下面我们就逐一解决这两个场景下的错误捕获问题。

处理 JS 的额外错误

我们可以用 BOM 提供的全局错误处理函数 window.onerror 来尝试捕获,它接收多个参数:

window.onerror = function (message, source, line, column, error) {
  console.log('全局捕获错误', message, source, line, column, error)
}

出错代码:

image.png

现在 JS 异常错误都可以被捕获到了,包括 setTimeout 宏任务的异步错误也可以被捕获,但我们注意到未被正常处理的 Promise 错误仍不能成功捕获。

处理 Promise 错误

参考 Vueerror.js 的代码,同步任务异常捕获就是套上一层 try...catch...,这也解释了为什么 Vue 捕获的错误不会被全局 window.onerror 再次捕获,因为已经在这里抛出了。而异步任务异常处理则是判断如果是 Promise 则把 catch 指向错误处理中:

image.png

我们可以模仿写一个插件,来处理 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额外异常捕获。

image.png

再试试上面的错误代码,看看结果:

created() {
    this.getSomeData();
},
methods: {
    async getSomeData() {
      await (asdasd = 1);
    },
},

image.png

可以被正常捕获,这种方式的好处是我们可以把发生错误的实例信息传进去,如果不想使用这种方法,或是在 Vue3 中使用 setup 方式而不是 options 写法,还可以使用全局的事件监听来捕获:

window.addEventListener('unhandledrejection', (event) => {
  console.log('全局捕获未处理的Promise异常', event)
})

image.png

完整代码

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')

结束

如果你需要更加丰富的错误收集分析功能,还是得使用如 SentryBugsnag 这类完善的错误追踪服务,不过相对来讲这些都需要不少配置部署操作。本文介绍了如何简单地在 Vue 中全局捕获异常错误,提升代码健壮性,且能避免在代码中编写大量异常捕获块,同时也减少了出错时控制台的大片飘红报警,收集错误可以帮助我们定位开发与测试阶段不易发现的疑难杂症,这部分可以使用 http 请求将错误信息发送到服务器。

以上就是文章的全部内容,希望对你有所帮助!如果觉得文章写的不错,可以点赞收藏,也欢迎关注,我会持续更新更多前端有用的知识与实用技巧,我是茶无味de一天,希望与你共同成长~

相关推荐

Java 8 新特性全面解读:解锁现代编程的魅力

Java8新特性全面解读:解锁现代编程的魅力Java8作为Java语言发展史上里程碑式的版本,不仅带来了诸多新特性,更深刻地改变了我们编写代码的方式。无论是简洁的Lambda表达式,还是强大的S...

教程:克隆公司饭卡 然后优雅地“蹭饭”

最近借了Proxmark3来娱乐性的玩下RFID技术,工资甚低的我只好拿公司饭卡实验,优雅地蹭几顿。物业大叔表打我啊!以下操作纯属学习目的,初学难免错误较多,望斧正。首先了解M1卡的结构:请参考htt...

新课标高中教材1030个短语大汇总(高中新课标英语词汇)

1.anamountof+不可数名词许多...

看完这篇文章你就懂 AQS 了(赛科龙aqs401参数详情)

前言谈到并发,我们不得不说AQS(AbstractQueuedSynchronizer),所谓的AQS即是抽象的队列式的同步器,内部定义了很多锁相关的方法,我们熟知的ReentrantLock、Ree...

Java 8新特性全面解析:开启现代化编程之旅

Java8新特性全面解析:开启现代化编程之旅Java8作为Java语言发展历程中的重要里程碑,在2014年正式发布。它的到来标志着Java从传统面向对象编程向支持函数式编程迈进的重要一步。在这一版...

Java 8新特性全面剖析:让代码更优雅、更高效

Java8新特性全面剖析:让代码更优雅、更高效Java8,作为Java语言发展史上的一座里程碑,带来了众多令人振奋的新特性。这些新特性不仅极大地提升了开发效率,还让代码变得更优雅、更简洁。今天,我...

线程安全编程方法总结(线程安全编程方法总结)

保护线程安全是多线程编程中的核心问题,关键在于控制共享资源的访问并协调线程间的执行顺序。以下是常用的基本方法及其适用场景:1.互斥锁(MutexLocks)机制:通过锁(如synchronize...

考勤数据标准化处理(考勤标准化管理制度)

经常做考勤的同学有没有遇到过这样的考勤数据呢?没错,从考勤机里导出来的,要统计那叫一个麻烦啊(有同学说可以导出txt文件,然后再处理会省事很多,有条件的小伙伴可以自己研究研究)看这表格数据其实挺规范的...

SQL语句大全,所有的SQL都在这里(sql语句百度百科)

一、基础1、说明:创建数据库CREATEDATABASEdatabase-name2、说明:删除数据库dropdatabasedbname3、说明:备份sqlserver---创建备份数...

手搓Agno智能体使用Ollama模型(ollama本地智能体)

轻量化智能体开源框架Agno有兴趣了解WhatisAgno-Agno安装python3.12...

高级排序算法之快速排序(高效排序)

前言今天继续算法学习,本次学习的是高级排序之快速排序。本文代码部分存在调用公共方法,可在文章:...

Linux cmp 命令使用详解(linux常用命令cp)

简介Linux中的...

N张图告诉你K-DB为什么能全面兼容Oracle?

不是每一款数据库都能全面兼容Oracle,就像不是所有数据库都可以被称之为K-DB。一般数据库能做到的SQL标准和函数上兼容Oracle,而K-DB则能实现更多,在数据库体系架构、集群方式、数据库对象...

12. Langchain评估与调试:用LangSmith优化模型表现

引言:从"感觉不错"到"数据说话"2025年某电商平台通过LangSmith系统化的评估优化,将客服机器人的问题解决率从68%提升至92%。本文将详解如何用...

那些有用但不为大家所熟知的 Java 特性

本文最初发表于PiotrMińkowski的个人站点Medium网站,经作者授权,由InfoQ中文站翻译分享。...

取消回复欢迎 发表评论: