当前位置: 首页 > news >正文

Vue 3 项目错误处理实战:Vue ErrorHandler、Promise 监控、用户友好提示

前言

一个健壮的错误处理机制可以提升用户体验,帮助开发者快速定位问题。今天分享如何实现完善的错误处理和监控!

错误分类

错误类型 ├── Vue 渲染错误 │ ├── 组件渲染错误 │ ├── 生命周期钩子错误 │ └── 模板语法错误 ├── JavaScript 运行时错误 │ ├── TypeError │ ├── ReferenceError │ └── 自定义业务错误 ├── 异步错误 │ ├── Promise rejection │ ├── setTimeout/setInterval │ └── Web API 错误 └── 资源加载错误 ├── 图片加载失败 ├── 脚本加载失败 └── API 请求失败

核心实现

1. 全局错误处理

// src/utils/errorHandler.tsimport{isDev}from'./env'// 错误日志服务classErrorLogger{privatelogs:ErrorLog[]=[]privatemaxLogs=100log(error:Error,context?:string){constlog:ErrorLog={id:`err_${Date.now()}`,message:error.message,stack:error.stack,context,timestamp:Date.now(),userAgent:navigator.userAgent,url:window.location.href}this.logs.unshift(log)// 只保留最近 N 条if(this.logs.length>this.maxLogs){this.logs=this.logs.slice(0,this.maxLogs)}// 开发环境输出到控制台if(isDev){console.error('[Error]',context,error)}// 上报到服务器(生产环境)if(!isDev){this.reportToServer(log)}}privateasyncreportToServer(log:ErrorLog){// 可以发送到你的日志服务try{awaitfetch('/api/error-report',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(log)})}catch(e){// 存储到 localStorage 作为后备this.saveToLocalStorage(log)}}privatesaveToLocalStorage(log:ErrorLog){constkey='error_logs'constexisting=JSON.parse(localStorage.getItem(key)||'[]')existing.push(log)localStorage.setItem(key,JSON.stringify(existing.slice(-50)))}getLogs():ErrorLog[]{returnthis.logs}}interfaceErrorLog{id:stringmessage:stringstack?:stringcontext?:stringtimestamp:numberuserAgent:stringurl:string}exportconsterrorLogger=newErrorLogger()

2. Vue 错误处理器

// src/main.tsimport{createApp}from'vue'importAppfrom'./App.vue'import{errorLogger}from'@/utils/errorHandler'constapp=createApp(App)// Vue 渲染错误处理app.config.errorHandler=(err,instance,info)=>{// err: 错误对象// instance: 发生错误的组件实例// info: 额外的错误信息(如生命周期钩子名称)console.error('Vue Error:',err)console.error('Component:',instance)console.error('Info:',info)errorLogger.log(err,`Vue Error [${info}]`)// 可以在这里显示用户友好的错误提示// showErrorToast('发生了一些问题,请稍后重试')}// 组件警告处理(开发环境)if(import.meta.env.DEV){app.config.warnHandler=(msg,instance,trace)=>{console.warn('Vue Warn:',msg)console.warn('Trace:',trace)}}// 异步错误处理app.config.asyncErrorHandler=(err,instance,info)=>{errorLogger.log(err,`Async Error [${info}]`)}app.mount('#app')

3. Promise 错误处理

// src/utils/promiseHandler.ts// 全局未处理的 Promise rejectionwindow.addEventListener('unhandledrejection',(event)=>{console.error('Unhandled Promise Rejection:',event.reason)errorLogger.log(event.reasoninstanceofError?event.reason:newError(String(event.reason)),'Unhandled Promise Rejection')// 阻止默认行为(显示控制台错误)event.preventDefault()})// 安全执行 PromiseexportasyncfunctionsafeAsync<T>(promise:Promise<T>,fallback?:T):Promise<T|undefined>{try{returnawaitpromise}catch(error){errorLogger.log(errorinstanceofError?error:newError(String(error)),'Safe Async')returnfallback}}// Promise 包装函数exportfunctionto<T>(promise:Promise<T>):Promise<[Error|null,T|null]>{returnpromise.then<[null,T]>((data)=>[null,data]).catch<[Error,null]>((err)=>[err,null])}

4. API 请求错误处理

// src/utils/request.tsimport{errorLogger}from'./errorHandler'import{ElMessage}from'element-plus'interfaceRequestOptions{showError?:booleanerrorContext?:string}exportasyncfunctionrequest<T>(url:string,options:RequestOptions={}):Promise<T>{const{showError=true,errorContext='API Request'}=optionstry{constresponse=awaitfetch(url)if(!response.ok){consterror=newError(`HTTP${response.status}:${response.statusText}`)errorLogger.log(error,errorContext)if(showError){ElMessage.error(`请求失败:${error.message}`)}throwerror}returnawaitresponse.json()}catch(error){if(errorinstanceofError){errorLogger.log(error,errorContext)if(showError&&!error.message.includes('HTTP')){ElMessage.error('网络请求失败,请检查网络连接')}}throwerror}}// 使用示例asyncfunctionfetchArticle(id:string){returnrequest<Article>(`/api/articles/${id}`,{showError:true,errorContext:'fetchArticle'})}

5. 资源加载错误处理

// src/composables/useResourceError.tsimport{onMounted,onErrorCaptured}from'vue'exportfunctionuseResourceError(){// 图片加载失败functionhandleImageError(event:Event){constimg=event.targetasHTMLImageElement img.src='/default-image.png'// 默认图片img.classList.add('error-loaded')}// 脚本加载失败functionloadScript(src:string):Promise<void>{returnnewPromise((resolve,reject)=>{constscript=document.createElement('script')script.src=src script.onload=()=>resolve()script.onerror=()=>{reject(newError(`Failed to load script:${src}`))}document.head.appendChild(script)})}// Vue 组件内错误捕获onErrorCaptured((err,instance,info)=>{console.error('Captured in component:',err)console.error('Component:',instance)console.error('Info:',info)// 返回 false 阻止错误继续传播returnfalse})return{handleImageError,loadScript}}

6. 错误边界组件

<!-- src/components/ErrorBoundary.vue --> <template> <slot v-if="!hasError" /> <div v-else class="error-boundary"> <div class="error-content"> <div class="error-icon">😢</div> <h2>页面出现了一些问题</h2> <p>别担心,这只是一个小插曲</p> <div class="error-actions"> <el-button type="primary" @click="handleRetry"> 重试一下 </el-button> <el-button @click="handleGoHome"> 返回首页 </el-button> </div> <details v-if="showDetails" class="error-details"> <summary>查看错误详情</summary> <pre>{{ errorMessage }}</pre> </details> </div> </div> </template> <script setup lang="ts"> import { ref, onErrorCaptured } from 'vue' import { useRouter } from 'vue-router' const router = useRouter() const hasError = ref(false) const errorMessage = ref('') const showDetails = ref(false) onErrorCaptured((err, instance, info) => { hasError.value = true errorMessage.value = `${err.message}\n\nComponent: ${instance?.$options?.name || 'Unknown'}\nInfo: ${info}` console.error('ErrorBoundary caught:', err) // 返回 false 阻止错误传播 return false }) function handleRetry() { hasError.value = false errorMessage.value = '' } function handleGoHome() { hasError.value = false router.push('/') } </script> <style scoped> .error-boundary { display: flex; align-items: center; justify-content: center; min-height: 400px; padding: 40px; } .error-content { text-align: center; max-width: 400px; } .error-icon { font-size: 64px; margin-bottom: 20px; } .error-content h2 { margin: 0 0 8px; font-size: 24px; } .error-content p { color: #666; margin-bottom: 24px; } .error-actions { display: flex; gap: 12px; justify-content: center; margin-bottom: 24px; } .error-details { text-align: left; margin-top: 20px; } .error-details pre { background: #f5f5f5; padding: 16px; border-radius: 8px; overflow-x: auto; font-size: 12px; } </style>

7. 使用 ErrorBoundary

<!-- src/App.vue --> <template> <ErrorBoundary> <router-view /> </ErrorBoundary> </template> <script setup lang="ts"> import ErrorBoundary from '@/components/ErrorBoundary.vue' </script>

错误监控服务

推荐工具

  1. Sentry- 功能强大的错误追踪服务
  2. FunDebug- 国产前端监控工具
  3. Badjs- 腾讯开源的前端监控

Sentry 集成

npminstall@sentry/vue @sentry/tracing
// src/utils/sentry.tsimport*asSentryfrom'@sentry/vue'import{BrowserTracing}from'@sentry/tracing'exportfunctioninitSentry(app:any){Sentry.init({app,dsn:'YOUR_SENTRY_DSN',integrations:[newBrowserTracing({routingInstrumentation:Sentry.vueRouterInstrumentation(router),}),],environment:import.meta.env.MODE,beforeSend(event){// 过滤非关键错误if(event.exception?.values?.[0]?.type==='AbortError'){returnnull}returnevent}})}

💡最佳实践

  • 始终在 Promise 后添加 .catch()
  • 使用 try-catch 包装异步代码
  • 提供用户友好的错误提示
  • 记录详细错误日志便于调试

🔗相关资源

  • Sentry:sentry.io
  • FunDebug:fundebug.com
http://www.jsqmd.com/news/687728/

相关文章:

  • 如何快速为所有Win32应用添加Mica效果:Mica For Everyone完整指南
  • 2026年4月广州客厅灯主灯/吸顶灯/卧室灯/卧室吸顶灯/灯具厂家解析 - 2026年企业推荐榜
  • 瑞祥商联卡回收快速变现技巧 - 团团收购物卡回收
  • 从Joomla 3.7.0 SQL注入到Root提权:一次完整的DC-3靶场实战复盘(附脚本下载)
  • 在线PH检测仪选型对比:哈希、梅特勒与国产头部品牌谁更值? - 陈工日常
  • 如何用ChanlunX缠论插件实现股票技术分析自动化:3步快速上手指南
  • 5分钟快速上手:通达信缠论分析插件完整指南
  • 闲置的永辉超市购物卡如何处理?掌握最便捷的回收方法 - 团团收购物卡回收
  • 感知机为什么是AI的‘Hello World’?聊聊它的历史、局限与在神经网络中的‘复活’
  • 2026车库门彩钢卷批发定制厂家实力测评:优质品牌推荐及选型指南 - 博客湾
  • CodeCombat游戏化编程学习实战指南:从零到一的完整成长路径
  • 终极解决方案:告别崩溃!QuickLook网络文件预览问题的完美修复指南
  • 传统文化+AI:春联生成模型-中文-base在文化教育场景的应用案例
  • 力扣Hot100(2)
  • PowerShell脚本转EXE终极指南:3分钟学会专业级打包
  • 马斯克的600亿阳谋:先绑Cursor,再决定吞不吞
  • 如何让QuickLook完美支持QOI图像格式快速预览:完整配置指南
  • 盘点2026年秦皇岛口碑不错的上门电缆回收企业,价格如何 - mypinpai
  • 5步掌握音乐解锁:Unlock-Music浏览器解密完整指南
  • 如何永久保存微信聊天记录:WeChatMsg终极数据备份指南
  • 沃尔玛购物卡回收最全教程! - 团团收购物卡回收
  • Windows系统配置自动化工具:WinUtil深度技术解析与实战指南
  • 2026 永辉超市卡回收指南:可可收正规渠道 + 最新价格全解析 - 可可收
  • 终极指南:解决Genesis项目LuisaRenderPy CUDA后端安装难题的完整方案
  • MW-N100-NAS主板解析:高性能迷你ITX存储解决方案
  • 探讨高性价比的钢管总成工厂,江苏地区靠谱品牌怎么选择 - 工业设备
  • 终极指南:drawio-desktop如何通过异常捕获机制保障你的绘图不崩溃
  • 2026重庆汽车贴膜优选测评:合规资质、施工、售后全维度打分 - 深度智识库
  • 别再花钱买教程了!手把手教你用B站免费视频搞定NVivo 12/14中文版安装与基础设置
  • 剖析2026年长春好用的污水罐制造商,卧式污水罐、污水深度处理罐多少钱 - myqiye