Axios + Vue 错误处理规范:中后台项目实战,统一捕获系统 / 业务 / 接口异常|API 与异步请求规范篇

【Axios+Vue错误处理】中后台项目实战:从错误分类到统一捕获,彻底搞定系统/业务/接口异常,避开重复提示与白屏坑!

📑 文章目录

  • 一、开篇:为什么要统一处理错误?
  • 二、先搞清楚:错误到底分哪几种?
    • [2.1 三种错误类型](#2.1 三种错误类型)
    • [2.2 系统异常:JS 跑崩了](#2.2 系统异常:JS 跑崩了)
    • [2.3 业务异常:业务逻辑告诉你"不行"](#2.3 业务异常:业务逻辑告诉你“不行”)
    • [2.4 接口错误:HTTP 或请求层出问题](#2.4 接口错误:HTTP 或请求层出问题)
  • 三、错误处理的核心原则
  • [四、实战:Axios 请求层封装](#四、实战:Axios 请求层封装)
    • [4.1 项目结构建议](#4.1 项目结构建议)
    • [4.2 完整请求封装示例](#4.2 完整请求封装示例)
    • [4.3 统一错误处理函数](#4.3 统一错误处理函数)
  • 五、业务层怎么处理"业务异常"?
    • [5.1 接口层统一处理 vs 业务层补充](#5.1 接口层统一处理 vs 业务层补充)
    • [5.2 业务层处理示例](#5.2 业务层处理示例)
    • [5.3 在 Vue 组件中使用](#5.3 在 Vue 组件中使用)
  • 六、全局兜底:系统异常(如白屏)怎么防?
    • [6.1 Vue 的 errorHandler](#6.1 Vue 的 errorHandler)
    • [6.2 全局未捕获的 Promise 异常](#6.2 全局未捕获的 Promise 异常)
    • [6.3 Vue 错误边界组件(Vue 3.4+)](#6.3 Vue 错误边界组件(Vue 3.4+))
  • 七、常见踩坑与避坑指南
    • [7.1 拦截器里 return 和 Promise.reject 搞混](#7.1 拦截器里 return 和 Promise.reject 搞混)
    • [7.2 重复提示用户](#7.2 重复提示用户)
    • [7.3 取消请求未处理](#7.3 取消请求未处理)
    • [7.4 不同后端约定的 code 不一样](#7.4 不同后端约定的 code 不一样)
  • 八、异步请求的推荐写法
    • [8.1 async/await + try/catch](#8.1 async/await + try/catch)
    • [8.2 并发请求](#8.2 并发请求)
    • [8.3 避免 async 没有 catch](#8.3 避免 async 没有 catch)
  • 九、小结:一张图帮你记牢
  • [🔍 系列模块导航](#🔍 系列模块导航)
    • [📝 API 与异步请求规范](#📝 API 与异步请求规范)
    • [📚 系列总览](#📚 系列总览)

同学们好,我是 Eugene(尤金),一名多年中后台前端开发工程师。

(Eugene 发音 /juːˈdʒiːn/,大家怎么顺口怎么叫就好)

很多前端开发者都会遇到一个瓶颈:

代码能跑,但不够规范;功能能实现,但维护起来特别痛苦;一个人写没问题,一到团队协作就各种混乱、踩坑、返工。

想写出干净、优雅、可维护 的专业代码,靠的不是天赋,而是体系化的规范 + 真实实战经验

这一系列《前端规范实战》,我会用大白话 + 真实业务场景,不讲玄学、不堆理论,只分享能直接落地的规范、标准与避坑指南。

帮你从「会写代码」真正升级为「会写优质、可维护、团队级别的代码」。


一、开篇:为什么要统一处理错误?

开发时常见两类人:一类"到处 try-catch",一类"基本不写 try-catch"。前者代码臃肿,后者一旦出错就白屏或控制台一片红。

规范错误处理的目的很简单:让错误有统一的去处,并能用统一、友好的方式提示给用户

这篇文章会围绕三件事展开:

  1. 错误分类:系统异常、业务异常、接口错误
  2. 统一捕获:在哪捕获、怎么捕获、如何上报
  3. 规范实践:Axios 封装、异步流程、Vue 项目中的落地方式

[⬆ 返回目录](#⬆ 返回目录)

二、先搞清楚:错误到底分哪几种?

2.1 三种错误类型

类型 举例 谁该处理 用户需要看到吗
系统异常 JS 报错、网络断开、请求超时 全局兜底 需要通用提示
业务异常 登录过期、余额不足、无权限 业务逻辑 需要具体文案
接口错误 4xx、5xx、接口返回错误码 请求层统一处理 视情况而定

[⬆ 返回目录](#⬆ 返回目录)

2.2 系统异常:JS 跑崩了

典型场景:空指针、类型错误、未定义变量等。

js 复制代码
// 典型的系统异常
const obj = null;
console.log(obj.name);  // ❌ Cannot read property 'name' of null

const arr = undefined;
arr.push(1);  // ❌ Cannot read property 'push' of undefined

这类错误会直接中断代码执行,需要全局兜底,避免白屏。

[⬆ 返回目录](#⬆ 返回目录)

2.3 业务异常:业务逻辑告诉你"不行"

典型场景:余额不足、登录失效、无权限等,接口一般会返回业务错误码和文案。

js 复制代码
// 接口返回的业务异常示例
{
  "code": 40001,
  "message": "余额不足,请先充值",
  "data": null
}

这类要由业务层识别并处理,通常要显示具体提示,而不是笼统的"请求失败"。

[⬆ 返回目录](#⬆ 返回目录)

2.4 接口错误:HTTP 或请求层出问题

典型场景:404、500、网络超时、CORS 等。

js 复制代码
// 常见的接口错误
// 1. 404 - 接口地址不存在
// 2. 500 - 服务端内部错误
// 3. 网络超时 - 用户网络不稳定
// 4. 请求被取消 - 用户快速切换页面导致

这类要在请求封装层统一处理,避免每个接口单独写错误逻辑。

[⬆ 返回目录](#⬆ 返回目录)

三、错误处理的核心原则

记住三条即可:

  1. 分层处理:全局兜底 + 请求层拦截 + 业务层补充
  2. 统一入口:错误都经过统一的处理函数,方便打日志、上报
  3. 用户友好:不直接展示技术报错,而是可读的提示文案

[⬆ 返回目录](#⬆ 返回目录)

四、实战:Axios 请求层封装

4.1 项目结构建议

复制代码
src/
├── api/
│   ├── request.js      # Axios 实例封装
│   ├── modules/        # 按业务模块拆分接口
│   │   ├── user.js
│   │   └── order.js
│   └── interceptors.js # 拦截器(可选,也可以写在 request.js)
├── utils/
│   ├── errorHandler.js # 统一错误处理
│   └── message.js      # 统一消息提示

[⬆ 返回目录](#⬆ 返回目录)

4.2 完整请求封装示例

js 复制代码
// src/api/request.js
import axios from 'axios';
import { showError, showWarning } from '@/utils/message';
import { handleError } from '@/utils/errorHandler';

// 创建 axios 实例
const request = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
  timeout: 15000,
  headers: {
    'Content-Type': 'application/json',
  },
});

// 请求拦截器
request.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem('token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// 响应拦截器 - 这里是错误处理的核心
request.interceptors.response.use(
  (response) => {
    const { data } = response;
    
    // 情况1:HTTP 200,但业务上可能失败(很多后端会这样设计)
    if (data.code !== undefined && data.code !== 0 && data.code !== 200) {
      // 业务异常:交给统一处理
      handleError({
        type: 'business',
        code: data.code,
        message: data.message || data.msg || '请求失败',
        response: response,
      });
      return Promise.reject(new Error(data.message || data.msg));
    }
    
    // 情况2:业务成功,直接返回 data
    return data;
  },
  (error) => {
    // 情况3:HTTP 非 2xx、网络错误、超时等
    handleError({
      type: 'network',
      error: error,
      response: error.response,
    });
    return Promise.reject(error);
  }
);

export default request;

[⬆ 返回目录](#⬆ 返回目录)

4.3 统一错误处理函数

js 复制代码
// src/utils/errorHandler.js

/**
 * 统一错误处理入口
 * @param {Object} params
 * @param {'business' | 'network'} params.type - 错误类型
 * @param {number} [params.code] - 业务错误码
 * @param {string} [params.message] - 错误信息
 * @param {Error} [params.error] - 原始错误对象
 * @param {Object} [params.response] - axios 响应对象
 */
export function handleError({ type, code, message, error, response }) {
  // 1. 用户提示(选合适的 UI 组件)
  const userMessage = getErrorMessage(type, code, message, error, response);
  showErrorToUser(userMessage);

  // 2. 开发环境打印,方便调试
  if (import.meta.env.DEV) {
    console.error('[ErrorHandler]', { type, code, message, error, response });
  }

  // 3. 生产环境上报(对接你们的监控平台)
  if (import.meta.env.PROD) {
    reportError({ type, code, message, error, response });
  }
}

/**
 * 根据错误类型和状态码,生成对用户友好的提示
 */
function getErrorMessage(type, code, message, error, response) {
  if (type === 'business') {
    return message || '操作失败,请稍后重试';
  }

  if (type === 'network') {
    if (!error) return '网络异常,请检查网络后重试';

    if (error.code === 'ECONNABORTED') {
      return '请求超时,请稍后重试';
    }
    if (error.message === 'Network Error') {
      return '网络连接失败,请检查网络';
    }
    if (error.message?.includes('canceled')) {
      return null; // 请求被取消,一般不提示
    }

    const status = response?.status;
    const statusMap = {
      400: '请求参数错误',
      401: '登录已过期,请重新登录',
      403: '没有权限访问',
      404: '请求的资源不存在',
      500: '服务器繁忙,请稍后重试',
      502: '服务暂时不可用',
      503: '服务暂时不可用',
    };
    return statusMap[status] || message || '请求失败,请稍后重试';
  }

  return '系统异常,请稍后重试';
}

/**
 * 对用户的提示(根据项目用的 UI 库替换)
 */
function showErrorToUser(msg) {
  if (!msg) return; // 取消等情况不提示
  
  // 如果用 Element Plus
  // import { ElMessage } from 'element-plus';
  // ElMessage.error(msg);
  
  // 如果用 Ant Design Vue
  // import { message } from 'ant-design-vue';
  // message.error(msg);
  
  // 简单示例:用原生 alert 或自定义 toast
  console.warn('[User Message]', msg);
}

/**
 * 错误上报(对接 Sentry、阿里云 ARMS 等)
 */
function reportError(payload) {
  // 示例:可接入 Sentry
  // Sentry.captureException(new Error(JSON.stringify(payload)));
  console.log('[Report]', payload);
}

[⬆ 返回目录](#⬆ 返回目录)

五、业务层怎么处理"业务异常"?

5.1 接口层统一处理 vs 业务层补充

场景 建议 说明
通用错误(401、500 等) 拦截器统一处理 避免到处写相同逻辑
特定业务错误(如余额不足) 业务层单独处理 需要特殊逻辑(如弹充值框)
静默失败 接口返回,不全局提示 例如列表加载失败只影响当前区域

[⬆ 返回目录](#⬆ 返回目录)

5.2 业务层处理示例

js 复制代码
// src/api/modules/order.js
import request from '../request';

/**
 * 创建订单
 * 业务层可以根据返回的 code 做特殊处理
 */
export function createOrder(data) {
  return request({
    url: '/order/create',
    method: 'post',
    data,
  }).catch((err) => {
    // 某些业务错误希望在调用方处理,可以不在这里 throw
    // 但大多数情况下,拦截器已经处理过了,这里只是补充逻辑
    if (err.message?.includes('库存不足')) {
      // 可以跳转库存页面、弹窗等
      console.log('跳转到库存不足提示页');
    }
    throw err; // 继续向上抛出,让调用方知道失败了
  });
}

[⬆ 返回目录](#⬆ 返回目录)

5.3 在 Vue 组件中使用

html 复制代码
<script setup>
import { ref } from 'vue';
import { createOrder } from '@/api/modules/order';

const loading = ref(false);
const handleSubmit = async () => {
  loading.value = true;
  try {
    const res = await createOrder({ productId: 123, count: 2 });
    // 成功后的逻辑
    console.log('订单创建成功', res);
    // 可以再弹一个成功提示
  } catch (err) {
    // 错误已经被拦截器处理并提示过了
    // 这里可以做:关闭 loading、重置表单、跳转等
    console.log('订单创建失败', err);
  } finally {
    loading.value = false;
  }
};
</script>

[⬆ 返回目录](#⬆ 返回目录)

六、全局兜底:系统异常(如白屏)怎么防?

6.1 Vue 的 errorHandler

Vue 3 提供了全局错误处理,适合兜底组件、生命周期、watch 等抛出的错误:

js 复制代码
// main.js 或 main.ts
import { createApp } from 'vue';
import App from './App.vue';

const app = createApp(App);

app.config.errorHandler = (err, instance, info) => {
  console.error('Vue 全局错误:', err);
  console.error('错误信息:', info);
  console.error('出错组件:', instance);
  
  // 用户提示
  // showError('页面出现异常,请刷新重试');
  
  // 上报
  // reportError({ type: 'vue', error: err, info });
};

app.mount('#app');

[⬆ 返回目录](#⬆ 返回目录)

6.2 全局未捕获的 Promise 异常

未处理的 Promise.rejectasync 抛错会变成 unhandledrejection,需要单独监听:

js 复制代码
// main.js 中追加
window.addEventListener('unhandledrejection', (event) => {
  console.error('未处理的 Promise 错误:', event.reason);
  event.preventDefault(); // 阻止默认控制台报错(可选)
  
  // 用户提示
  // showError('操作失败,请稍后重试');
  
  // 上报
  // reportError({ type: 'unhandledrejection', reason: event.reason });
});

[⬆ 返回目录](#⬆ 返回目录)

6.3 Vue 错误边界组件(Vue 3.4+)

Vue 3.4 起支持 onErrorCaptured,可以做到局部错误边界,避免整个应用崩溃:

html 复制代码
<!-- ErrorBoundary.vue -->
<template>
  <div v-if="hasError" class="error-fallback">
    <p>哎呀,这部分内容加载出错了</p>
    <button @click="retry">重试</button>
  </div>
  <slot v-else />
</template>

<script setup>
import { ref, onErrorCaptured } from 'vue';

const hasError = ref(false);

onErrorCaptured((err) => {
  hasError.value = true;
  console.error('子组件错误被捕获:', err);
  return false; // 阻止继续向上冒泡
});

const retry = () => {
  hasError.value = false;
};
</script>

[⬆ 返回目录](#⬆ 返回目录)

七、常见踩坑与避坑指南

7.1 拦截器里 return 和 Promise.reject 搞混

js 复制代码
// ❌ 错误:业务失败时直接 return,调用方拿不到错误
request.interceptors.response.use((response) => {
  if (data.code !== 0) {
    showError(data.message);
    return; // 危险!调用方 await 会得到 undefined
  }
  return data;
});

// ✅ 正确:必须 reject,调用方才能在 catch 里处理
request.interceptors.response.use(
  (response) => {
    const { data } = response;
    if (data.code !== 0) {
      showError(data.message);
      return Promise.reject(new Error(data.message));
    }
    return data;
  },
  (error) => Promise.reject(error)
);

[⬆ 返回目录](#⬆ 返回目录)

7.2 重复提示用户

js 复制代码
// ❌ 拦截器已经 showError,组件里又 showError
// 拦截器
handleError({ message: '登录过期' }); // 弹了一次

// 组件
catch (err) {
  showError('登录过期'); // 又弹一次!
}

// ✅ 拦截器统一提示,组件只做业务逻辑(如跳转登录)
catch (err) {
  if (err.message?.includes('登录')) {
    router.push('/login');
  }
}

[⬆ 返回目录](#⬆ 返回目录)

7.3 取消请求未处理

快速切换 Tab 时,前一个请求可能还在进行,需要取消:

js 复制代码
import axios from 'axios';

// 用 CancelToken 或 AbortController
const controller = new AbortController();

request.get('/api/list', {
  signal: controller.signal,
});

// 组件卸载或切换时
onUnmounted(() => {
  controller.abort();
});

在拦截器里,对 canceled 错误应避免再弹错误提示,只做静默处理。

[⬆ 返回目录](#⬆ 返回目录)

7.4 不同后端约定的 code 不一样

有的用 0 表示成功,有的用 200,有的用 success: true,建议在 request.js 里抽象一层:

js 复制代码
// 抽象成配置
const SUCCESS_CODES = [0, 200];
const isSuccess = (code) => SUCCESS_CODES.includes(code);

// 或者从 data 里读
const isSuccess = (data) => data.code === 0 || data.success === true;

[⬆ 返回目录](#⬆ 返回目录)

八、异步请求的推荐写法

8.1 async/await + try/catch

js 复制代码
async function fetchData() {
  try {
    const res = await getList();
    // 处理 res
  } catch (err) {
    // 可选:拦截器已提示,这里做业务补充
  } finally {
    loading.value = false;
  }
}

[⬆ 返回目录](#⬆ 返回目录)

8.2 并发请求

js 复制代码
// ✅ 用 Promise.all,一个失败整体失败
const [users, orders] = await Promise.all([
  getUserList(),
  getOrderList(),
]);

// 需要"部分成功也算成功"时用 Promise.allSettled
const results = await Promise.allSettled([api1(), api2()]);

[⬆ 返回目录](#⬆ 返回目录)

8.3 避免 async 没有 catch

js 复制代码
// ❌ 危险
async function init() {
  const data = await fetchData(); // 一旦失败,没有 catch
  doSomething(data);
}
init();

// ✅ 要么加 catch,要么在调用处包一层
init().catch(handleError);

[⬆ 返回目录](#⬆ 返回目录)

九、小结:一张图帮你记牢

复制代码
用户操作
    ↓
组件调用 API
    ↓
Axios 请求 → 请求拦截器(加 token 等)
    ↓
服务器响应
    ↓
响应拦截器
    ├─ HTTP 2xx + 业务成功 → 返回 data
    ├─ HTTP 2xx + 业务失败 → handleError(business) → reject
    └─ HTTP 非 2xx / 网络错误 → handleError(network) → reject
    ↓
handleError:提示用户 + 开发日志 + 生产上报
    ↓
组件 catch:业务逻辑(跳转、重置等)
    ↓
全局兜底:Vue errorHandler + unhandledrejection

记住三点即可:

  1. 请求层:统一处理 HTTP 与业务错误,避免到处重复写。
  2. 业务层:只做特殊逻辑,不要重复弹提示。
  3. 全局 :用 errorHandlerunhandledrejection 兜底,防止白屏和未捕获异常。

[⬆ 返回目录](#⬆ 返回目录)

🔍 系列模块导航

📝 API 与异步请求规范

一、《Axios 统一封装实战:拦截器配置 + baseURL 优化 + 接口规范,避坑重复代码|API 与异步请求规范篇》
二、《Axios 接口请求规范实战:请求参数 / 响应处理 / 异常兜底,避坑中后台 API 调用混乱|API 与异步请求规范篇》

三、《Axios + Vue 错误处理规范:中后台项目实战,统一捕获系统 / 业务 / 接口异常|API 与异步请求规范篇》
四、《前端实战:Excel 导入导出规范(命名 + 校验 + 错误处理 + 统一交互)|API 与异步请求规范篇》

👉 跟着系列慢慢学,把技术功底扎扎实实地打牢~

📚 系列总览

前端体系化学习完全体:基础 → 规范 → 架构 → 大厂面试

四套系列、百余篇高质量实战文,从入门到进阶,一站式补齐前端核心能力

每个系列完结后,都会整理成一篇完整导航文并附上直达链接,方便大家按顺序、体系化学习。

全套内容持续更新中,敬请期待~

[⬆ 返回目录](#⬆ 返回目录)


技术成长,从来不是比谁写得快,而是比谁写得稳、规范、可维护

哪怕每次只吃透一条规范,长期下来,差距会非常明显。

后续我会持续更新前端规范、工程化、可维护代码相关实战干货,帮你告别面条代码、维护噩梦,在开发与面试中更有底气。

觉得有用欢迎 点赞 + 收藏 + 关注,不错过每一篇实战内容。

我是 Eugene,与你一起写规范、写优质代码,我们下篇干货见~

相关推荐
行走的陀螺仪1 小时前
手写 Vue3 极简 i18n
前端·javascript·vue.js·国际化·i18n
羽沢311 小时前
一篇简单的STOMP教程QAQ
前端·javascript·stomp
code_Bo1 小时前
使用AI完成Swagger接口类型在前端自动生成的工具
前端·后端·架构
加个鸡腿儿2 小时前
从"包裹器"到"确认按钮"——一个组件的三次重构
前端·vue.js·设计模式
Kel2 小时前
深入 OpenAI Node SDK:一个请求的奇幻漂流
javascript·人工智能·架构
子兮曰2 小时前
AI写代码坑了90%程序员!这5个致命bug,上线就炸(附避坑清单)
前端·javascript·后端
猪八宅百炼成仙2 小时前
PanelSplitter 组件:前端左右布局宽度调整的实用解决方案
前端
BUG胡汉三2 小时前
自建在线文档编辑服务:基于 Collabora CODE + Spring Boot + Vue 3 的完整实现
vue.js·spring boot·后端·在线编辑
锋利的绵羊2 小时前
【解决方案】微信浏览器跳出到浏览器打开、跳转到app,安卓&ios
前端