你的Axios还停留在请求拦截器添加header,响应拦截器处理状态码嘛

在前端开发中封装axios是提升代码复用性、统一错误处理逻辑和优化用户体验的重要实践,但如果只是简单的在请求拦截器中添加token,响应拦截器中处理状态码是不能满足真正的封装需求的

那么好的axios封装应该满足那些点,个人觉得应该需要的如下(大佬饶命)

自动附加认证信息(token、cookie)如果是cookie-session方案要注意Set-Cookie大小写问题
js 复制代码
   const setCookie: string = header['Set-Cookie'] || header['set-cookie']
请求与响应拦截,支持统一错误处理
js 复制代码
// 在常量文件中维护特殊状态码 根据实际业务
const SPECIAL_CODE = 5201314
const CODE_200 = 200
const CODE_401 = 401
const CODE_0 = 0
支持取消请求,避免竞态条件

主要处理竞态问题 比如搜索框场景 搜a ab a的搜索请求会被取消

防止重复请求

性能优化 通过构造唯一key以及PendingMap

支持请求自动重试,提高稳定性

多给几次机会防止网络波动影响用户体验

兼容 TypeScript,支持泛型推导

主要看当前项目使用的框架

同一时间只弹出一个错误提示

用户体验

完整代码
js 复制代码
import axios, {
  AxiosInstance,
  AxiosRequestConfig,
  AxiosResponse,
  AxiosError,
  CancelTokenSource
} from 'axios';
import { ElMessage } from 'element-plus';

// ------ 常量定义 ------ //
const CODE_0 = 0;           // 后端业务成功 code
const CODE_401 = 401;       // 业务层登录失效
const SPECIAL_CODE = 5201314; // 示例特殊业务码

// ------ 扩展 AxiosConfig 类型 ------ //
export interface CustomRequestConfig extends AxiosRequestConfig {
  retry?: number;          // 重试次数
  retryDelay?: number;     // 重试间隔(ms)
}

// ------ 创建 Axios 实例 ------ //
const service: AxiosInstance = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 6000,
  withCredentials: true,
});

// ------ 存储 pending 请求的映射(用于去重 & 取消) ------ //
const pendingMap = new Map<string, CancelTokenSource>();

// ------ 全局错误提示开关 ------ //
let isErrorVisible = false;
function showMessage(msg: string) {
  if (!isErrorVisible && msg) {
    isErrorVisible = true;
    ElMessage.error({
      message: msg,
      onClose: () => {
        isErrorVisible = false;
      }
    });
  }
}

// ------ 生成请求唯一 key ------ //
function getReqKey(config: AxiosRequestConfig): string {
  const { method, url, params, data } = config;
  return [method, url, JSON.stringify(params), JSON.stringify(data)].join('&');
}

// ------ 移除 pending & 取消 token ------ //
function removePending(config: AxiosRequestConfig) {
  const key = getReqKey(config);
  if (pendingMap.has(key)) {
    pendingMap.get(key)!.cancel('cancel duplicate request');
    pendingMap.delete(key);
  }
}

// ------ 请求拦截 ------ //
service.interceptors.request.use(
  (config: CustomRequestConfig) => {
    // 1. 自动注入 Token
    const token = localStorage.getItem('token');
    if (token && config.headers) {
      config.headers.Authorization = `Bearer ${token}`;
    }

    // 2. 去重 & 取消前一个同 key 请求
    removePending(config);
    const source = axios.CancelToken.source();
    config.cancelToken = source.token;
    pendingMap.set(getReqKey(config), source);

    return config;
  },
  (error) => Promise.reject(error)
);

// ------ 响应拦截 ------ //
service.interceptors.response.use(
  async (response: AxiosResponse) => {
    // 收到响应后,先清理 pending
    removePending(response.config);

    // 处理 Set-Cookie 大小写兼容
    const setCookie = response.headers['set-cookie'] || response.headers['Set-Cookie'];
    if (setCookie) {
      console.log('Set-Cookie:', setCookie);
    }

    // HTTP 状态检查
    if (response.status === 200) {
      const res = response.data;
      // 业务 code 处理
      if (typeof res.code === 'number' && res.code !== CODE_0) {
        if (res.code === CODE_401) {
          showMessage('您未登录或登录已过期,请重新登录');
          // TODO: 跳转登录页
        } else if (res.code === SPECIAL_CODE) {
          showMessage(res.message || '出现特殊错误');
        } else {
          showMessage(res.message || '请求返回错误');
        }
        return Promise.reject(new Error(res.message));
      }
      return res.data;
    } else {
      showMessage(`网络错误:${response.status}`);
      return Promise.reject(new Error(`Status code: ${response.status}`));
    }
  },
  async (error: AxiosError) => {
    const config = error.config as CustomRequestConfig;
    // 1. 清理 pending
    if (config) removePending(config);

    // 2. 主动取消
    if (axios.isCancel(error)) {
      console.error('请求被取消:' + error.message);
      return Promise.reject(error);
    }

    // 3. 自动重试
    if (config && config.retry && config.retry > 0) {
      config.retry!--;
      await new Promise(res => setTimeout(res, config.retryDelay || 1000));
      return service(config);
    }

    // 4. 详细的状态码 & 网络错误处理
    let message = '';
    if (error.response) {
      switch (error.response.status) {
        case 302: message = '接口重定向了!'; break;
        case 400: message = '参数不正确!'; break;
        case 401: message = '您未登录,或者登录已经超时,请先登录!'; break;
        case 403: message = '您没有权限操作!'; break;
        case 404: message = `请求地址出错: ${error.response.config.url}`; break;
        case 408: message = '请求超时!'; break;
        case 409: message = '系统已存在相同数据!'; break;
        case 500: message = '服务器内部错误!'; break;
        case 501: message = '服务未实现!'; break;
        case 502: message = '网关错误!'; break;
        case 503: message = '服务不可用!'; break;
        case 504: message = '服务暂时无法访问,请稍后再试!'; break;
        case 505: message = 'HTTP版本不受支持!'; break;
        default:  message = '异常问题,请联系管理员!'; break;
      }
    }
    // 超时 & 离线提示
    if (error.message.includes('timeout')) message = '网络请求超时!';
    if (error.message.includes('Network')) {
      message = window.navigator.onLine ? '服务端异常!' : '您断网了!';
    }
    showMessage(message);

    return Promise.reject(error);
  }
);

// ------ 泛型 request 封装 ------ //
export function request<T = any>(config: CustomRequestConfig): Promise<T> {
  return service.request<any, T>(config);
}

// ------ RESTful 方法 ------ //
export function get<T = any>(
  url: string,
  params?: object,
  config?: Omit<CustomRequestConfig, 'url' | 'method' | 'params'>
): Promise<T> {
  return request<T>({ url, method: 'GET', params, ...config });
}

export function post<T = any>(
  url: string,
  data?: any,
  config?: Omit<CustomRequestConfig, 'url' | 'method' | 'data'>
): Promise<T> {
  return request<T>({ url, method: 'POST', data, ...config });
}

export function delete<T = any>(
  url: string,
  params?: object,
  config?: Omit<CustomRequestConfig, 'url' | 'method' | 'params'>
): Promise<T> {
  return request<T>({ url, method: 'DELETE', params, ...config });
}

欢迎大佬们给出实际业务中还需要注意的点以及坑,学习~

相关推荐
崔庆才丨静觅9 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60619 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了10 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅10 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅10 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅10 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment10 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅11 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊11 小时前
jwt介绍
前端
爱敲代码的小鱼11 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax