83. 统一错误处理机制

概述

在前后端分离的现代Web应用中,错误处理是确保应用稳定性和用户体验的关键环节。本集将深入探讨Vue 3项目中的统一错误处理机制,包括Axios请求错误、Vue组件错误、Pinia状态管理错误以及全局错误捕获。我们将学习如何设计一套完整的错误处理体系,确保应用在各种异常情况下都能优雅地处理并向用户提供友好的反馈。

核心知识点

1. Axios统一错误处理

Axios提供了拦截器机制,可以在请求发送前和响应返回后进行拦截处理,这是实现统一错误处理的理想位置。

// src/utils/axios.ts
import axios from 'axios'
import { ElMessage } from 'element-plus'

const http = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 10000
})

// 响应拦截器
http.interceptors.response.use(
  (response) => {
    // 处理业务逻辑错误(例如:后端返回code !== 200)
    if (response.data.code !== 200) {
      ElMessage.error(response.data.message || '请求失败')
      return Promise.reject(new Error(response.data.message || '请求失败'))
    }
    return response.data
  },
  (error) => {
    // 处理网络错误、超时等
    let errorMessage = '网络请求失败'
    if (error.code === 'ECONNABORTED') {
      errorMessage = '请求超时,请稍后重试'
    } else if (error.response) {
      // 服务器返回错误状态码
      const status = error.response.status
      switch (status) {
        case 400:
          errorMessage = '请求参数错误'
          break
        case 401:
          errorMessage = '未授权,请重新登录'
          // 跳转到登录页
          // router.push('/login')
          break
        case 403:
          errorMessage = '拒绝访问'
          break
        case 404:
          errorMessage = '请求资源不存在'
          break
        case 500:
          errorMessage = '服务器内部错误'
          break
        default:
          errorMessage = `请求失败 (${status})`
      }
    }
    
    ElMessage.error(errorMessage)
    return Promise.reject(error)
  }
)

export default http

2. Vue全局错误捕获

Vue 3提供了全局错误处理API,可以捕获组件渲染和生命周期钩子中的错误。

// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import { ElMessage } from 'element-plus'

const app = createApp(App)

// 全局错误处理
app.config.errorHandler = (err, instance, info) => {
  console.error('Vue全局错误:', err)
  console.error('错误实例:', instance)
  console.error('错误信息:', info)
  
  // 可以将错误上报到监控系统
  // reportError(err, info)
  
  // 向用户显示友好的错误信息
  ElMessage.error('系统发生错误,请稍后重试')
}

// 全局警告处理
app.config.warnHandler = (msg, instance, trace) => {
  console.warn('Vue全局警告:', msg)
  console.warn('警告实例:', instance)
  console.warn('警告追踪:', trace)
}

app.use(router).use(store).mount('#app')

3. Pinia状态管理错误处理

在Pinia的Actions中处理异步操作时,需要妥善处理错误,确保状态管理的稳定性。

// src/stores/user.ts
import { defineStore } from 'pinia'
import http from '../utils/axios'

export const useUserStore = defineStore('user', {
  state: () => ({
    userInfo: null,
    loading: false
  }),
  
  actions: {
    async fetchUserInfo() {
      try {
        this.loading = true
        const data = await http.get('/api/user/info')
        this.userInfo = data.data
      } catch (error) {
        console.error('获取用户信息失败:', error)
        // 可以在这里处理特定业务错误
        throw error // 重新抛出错误,让调用者可以继续处理
      } finally {
        this.loading = false
      }
    }
  }
})

4. 组件级错误处理

使用Vue 3的<error-boundary>组件(或自定义错误边界)可以捕获子组件树中的错误,防止整个应用崩溃。

<!-- src/components/ErrorBoundary.vue -->
<template>
  <slot v-if="!error" />
  <div class="error-container" v-else>
    <h3>哎呀,出错了!</h3>
    <p>{{ errorMessage }}</p>
    <button @click="resetError">重试</button>
  </div>
</template>

<script setup lang="ts">
import { ref, onErrorCaptured } from 'vue'

const props = defineProps<{
  fallbackMessage?: string
}>()

const emit = defineEmits<{
  error: [error: Error, instance: any, info: string]
}>()

const error = ref(false)
const errorMessage = ref('')

const resetError = () => {
  error.value = false
  errorMessage.value = ''
}

onErrorCaptured((err: Error, instance, info) => {
  error.value = true
  errorMessage.value = props.fallbackMessage || '组件发生错误'
  emit('error', err, instance, info)
  console.error('组件错误:', err, info)
  return false // 阻止错误继续向上传播
})
</script>

<style scoped>
.error-container {
  padding: 20px;
  background-color: #fef0f0;
  border: 1px solid #fbc4ab;
  border-radius: 4px;
  text-align: center;
  color: #f56c6c;
}

button {
  margin-top: 10px;
  padding: 8px 16px;
  background-color: #409eff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

button:hover {
  background-color: #66b1ff;
}
</style>

5. 异步错误处理

在Vue 3的组合式API中,处理异步操作时需要特别注意错误捕获。

<!-- src/components/UserProfile.vue -->
<template>
  <div v-if="loading" class="loading">加载中...</div>
  <ErrorBoundary @error="handleComponentError">
    <div v-else-if="userInfo" class="user-profile">
      <h2>{{ userInfo.name }}</h2>
      <p>{{ userInfo.email }}</p>
    </div>
    <div v-else class="empty">暂无用户信息</div>
  </ErrorBoundary>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useUserStore } from '../stores/user'
import ErrorBoundary from './ErrorBoundary.vue'

const userStore = useUserStore()
const loading = ref(true)

const handleComponentError = (err: Error) => {
  console.error('用户信息组件错误:', err)
}

onMounted(async () => {
  try {
    await userStore.fetchUserInfo()
  } catch (error) {
    console.error('获取用户信息失败:', error)
    // 这里可以添加额外的错误处理逻辑
  } finally {
    loading.value = false
  }
})
</script>

最佳实践

1. 分层错误处理策略

  • 数据层:Axios拦截器处理网络请求错误和业务逻辑错误
  • 状态层:Pinia Actions中处理异步操作错误
  • 组件层:使用错误边界捕获组件渲染错误
  • 应用层:全局错误处理器捕获未处理的错误

2. 错误日志上报

将错误信息上报到监控系统,便于开发人员分析和修复问题。

// src/utils/errorReport.ts
import * as Sentry from '@sentry/vue'
import { BrowserTracing } from '@sentry/tracing'

export const initErrorReporting = (app: any, router: any) => {
  Sentry.init({
    app,
    dsn: import.meta.env.VITE_SENTRY_DSN,
    integrations: [
      new BrowserTracing({
        routingInstrumentation: Sentry.vueRouterInstrumentation(router),
        tracingOrigins: ['localhost', import.meta.env.VITE_API_BASE_URL, /^https:\/\/your-domain\.com\//]
      })
    ],
    tracesSampleRate: 1.0
  })
}

export const reportError = (error: Error, extraInfo?: any) => {
  Sentry.captureException(error, {
    extra: extraInfo
  })
}

3. 友好的用户反馈

  • 对于可恢复的错误,提供重试机制
  • 对于不可恢复的错误,引导用户刷新页面或联系客服
  • 使用不同的反馈方式:Toast消息、错误页面、对话框等

4. 错误分类与优先级

  • 致命错误:导致应用崩溃的错误,需要立即修复
  • 严重错误:影响核心功能的错误,需要高优先级处理
  • 普通错误:影响非核心功能的错误,可以按计划修复
  • 警告信息:不影响功能,但需要注意的潜在问题

常见问题与解决方案

1. 问题:Axios拦截器中的错误没有被捕获

解决方案:确保在Axios拦截器中正确返回Promise.reject(),并且在组件中使用try/catch捕获异步操作错误。

2. 问题:全局错误处理器没有捕获到某些错误

解决方案

  • 确保正确设置了app.config.errorHandler
  • 注意异步操作中的错误需要手动捕获
  • 检查是否有其他错误处理器阻止了错误传播

3. 问题:错误边界组件没有捕获到子组件的错误

解决方案

  • 确保错误边界组件正确实现了onErrorCaptured钩子
  • 注意错误边界只能捕获同步错误和生命周期钩子中的错误
  • 异步操作中的错误需要手动捕获

4. 问题:生产环境中的错误信息不完整

解决方案

  • 使用Source Map确保错误堆栈信息的准确性
  • 配置错误监控系统,收集完整的错误上下文信息
  • 在生产环境中保留必要的错误日志,但避免泄露敏感信息

进一步学习资源

  1. Vue 3 错误处理官方文档
  2. Axios 错误处理文档
  3. Sentry Vue 集成文档
  4. 前端错误监控最佳实践
  5. 谈谈前端错误监控

课后练习

  1. 基础练习

    • 在你的Vue 3项目中实现Axios统一错误处理
    • 添加Vue全局错误处理器
    • 创建一个自定义的错误边界组件
  2. 进阶练习

    • 集成Sentry或其他错误监控系统
    • 实现错误日志的分级上报机制
    • 设计一套完整的错误反馈UI组件库
  3. 挑战练习

    • 实现错误重试机制,对于临时性网络错误自动重试
    • 设计错误恢复策略,允许用户在不刷新页面的情况下恢复应用状态
    • 创建错误分析仪表板,展示应用的错误统计信息

通过本集的学习,你应该能够建立一套完整的统一错误处理机制,确保你的Vue 3应用在各种异常情况下都能保持稳定运行,并向用户提供友好的反馈。错误处理是一个持续优化的过程,需要根据应用的实际情况不断调整和完善。

« 上一篇 82-request-response-interceptors 下一篇 » 取消请求与防抖节流 - Vue 3性能优化技术