人人都会AI编程

请求 / 响应拦截器、全局配置、错误统一处理

更新时间:2026-07-11

在实际项目中,直接使用 axios.get('/api/users') 虽然能跑通,但很快就会遇到三个问题:每个请求都要带 token,处理失败时要写一堆重复的错误提示,接口基准地址一换就要改几十个文件。Axios 提供了拦截器默认配置机制,让我们可以在一处集中处理这些通用逻辑,真正做到“一次封装,全局受益”。

创建实例与全局配置

首先,不再直接引入 axios,而是创建一个独立实例,为它设置项目专属的默认配置。

// src/utils/request.js
import axios from 'axios'

const request = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL, // 从环境变量读取,如 '/api'
  timeout: 10000,                             // 超时时间 10s
  headers: {
    'Content-Type': 'application/json'        // 默认请求头
  }
})

export default request

这样做的好处很明显:整个项目都通过 request 发送请求,以后改接口地址、调整超时只需要修改一处。环境变量让开发/测试/生产的环境切换零改代码。

请求拦截器:发请求前自动处理

请求拦截器在所有请求发出前被调用,适合处理全局性的前置逻辑,比如自动携带用户 token。

// 注入请求拦截器
request.interceptors.request.use(
  (config) => {
    // 从状态管理或本地存储中获取 token
    const token = localStorage.getItem('token')
    if (token) {
      config.headers.Authorization = `Bearer ${token}`
    }
    return config
  },
  (error) => {
    return Promise.reject(error)
  }
)

典型应用场景:

  • 自动追加 token、租户 ID、语言等统一参数
  • 根据请求方法统一处理 Content-Type(如文件上传改为 multipart/form-data
  • 发送请求时显示全局 loading 动画
  • 打印请求日志(开发调试用)

注意:拦截器中的 config 必须返回,否则请求会被挂起。

响应拦截器:统一处理返回数据与错误

响应拦截器在请求得到响应后、业务代码拿到数据前执行,适合做数据剥离、错误集中处理

request.interceptors.response.use(
  (response) => {
    const res = response.data
    // 根据后端约定的格式判断业务是否成功
    if (res.code !== 200) {
      // 业务错误
      ElMessage.error(res.message || '请求失败')
      return Promise.reject(new Error(res.message || 'Error'))
    }
    // 剥离出实际数据,业务代码直接拿到 data
    return res.data
  },
  (error) => {
    // 处理 HTTP 层面的错误(如 401、404、500)
    const message = error.response?.data?.message || error.message || '网络异常'
    ElMessage.error(message)
    return Promise.reject(error)
  }
)

响应拦截器的常见分层处理

  1. 剥离 response.data.data

后端通常返回 { code: 200, data: [...], message: 'ok' },在拦截器里直接返回 res.data,业务代码就不用每次都点一层 data。

  1. 业务错误统一弹窗

当 code 不为成功码时,直接弹出错误消息,业务代码只需要正常处理成功结果即可,不用重复写 if (res.code !== 200)

  1. HTTP 状态码处理

401(未登录)可以跳转到登录页并清空本地 token;403(无权限)可以提示“您没有操作权限”;500 类错误提示服务器异常。

  1. 日志上报

在拦截器中捕获异常并上报到监控平台,便于排查线上问题。

错误统一处理与分离

上面的拦截器虽然处理了错误,但有时业务本身也需要对某些请求单独处理异常(如表单校验失败不需要全局弹窗)。此时可以在具体请求的 catch 中阻止全局错误

// 某个 API 的调用
import request from '@/utils/request'

export function getUserList(params) {
  return request.get('/users', { params }).catch((err) => {
    // 如果业务代码已经 catch,则不再向上抛出,避免全局拦截器再次提示
    if (err.__handled) return Promise.reject(err)
    err.__handled = true
    throw err
  })
}

更优雅的做法是,在全局拦截器里判断错误类型或用一个标志位,让业务代码能“吞掉”某个错误。

为什么这样封装?

把请求/响应拦截器和全局配置集中在一个文件中,带来的好处是:

  • 代码简洁:业务代码消除大量重复的 token 拼接、错误提示、状态判断。
  • 行为一致:全站的接口异常处理、loading 状态都按统一逻辑运行,用户体验统一。
  • 易于维护:后端改了响应格式?换个拦截器即可,无需改动几十个接口文件。
  • 安全性:token、签名等敏感逻辑集中控制,避免因业务代码疏漏导致安全隐患。

一个真实的教训:某项目初期没有统一封装,登录过期后每个页面都需要单独处理 401,结果有的页面直接报错白屏,有的弹了个无法操作的过期提示,用户投诉不断。后来引入上述封装,只改一行拦截器代码,全站问题消失。

这套封装是 Vue 项目中数据请求的基石,后续的“请求取消”、“失败重试”、“接口防抖”等都是在这个基础上进一步叠加的能力。