Axios拦截器:前端通信的交通警察[特殊字符]

发布于:2025-09-02 ⋅ 阅读:(14) ⋅ 点赞:(0)


在这里插入图片描述

Axios拦截器:前端通信的"交通警察"🚦

引言:为什么需要拦截器?

想象一下你是一个城市的交通管理员,每天有成千上万的车辆(HTTP请求)在城市(你的应用)中穿梭。如果没有交通信号灯和交警(拦截器),这些车辆会乱成一团:

  • 每辆车都要自己找路(每个请求手动处理)
  • 遇到事故(错误)没有统一处理
  • 特殊车辆(特殊请求)没有优先通道

Axios拦截器就像是这个城市的智能交通管理系统,它能:

  1. 统一管理所有进出的请求和响应
  2. 自动处理重复性工作(如添加token)
  3. 集中处理错误和异常
  4. 监控和记录请求过程

拦截器的作用与好处

作用概览表

拦截器类型 主要作用 典型应用场景
请求拦截器 在请求发送前处理 添加认证token、设置请求头、参数加密
响应拦截器 在响应返回后处理 统一错误处理、数据格式化、响应解密

五大核心好处

  1. 代码复用:避免在每个请求中重复相同代码
  2. 集中管理:统一处理认证、错误等逻辑
  3. 流程控制:可以中断或修改请求/响应流程
  4. 增强可读性:业务代码更专注于业务本身
  5. 便于维护:修改拦截逻辑只需改一处

拦截器的工作原理

流程图解

[发起请求] → 
[请求拦截器] → 
[实际发送请求] → 
[服务器处理] → 
[响应返回] → 
[响应拦截器] → 
[最终数据处理]

原理详解

Axios内部维护了两个拦截器链(数组):

  1. 请求拦截器链:按添加顺序执行(先添加先执行)
  2. 响应拦截器链:按添加逆序执行(先添加后执行)

当发起请求时,Axios会:

  1. 创建一个Promise链
  2. 依次执行请求拦截器
  3. 发送实际HTTP请求
  4. 依次执行响应拦截器
  5. 返回最终处理结果

拦截器的多种写法与实践

1. 基础写法

// 添加请求拦截器
axios.interceptors.request.use(
  function (config) {
    // 在发送请求之前做些什么
    console.log('请求拦截器 - 处理请求配置', config);
    config.headers.Authorization = 'Bearer your_token'; // 添加token
    return config; // 必须返回处理后的config
  }, 
  function (error) {
    // 对请求错误做些什么
    console.log('请求拦截器 - 错误处理', error);
    return Promise.reject(error);
  }
);

// 添加响应拦截器
axios.interceptors.response.use(
  function (response) {
    // 对响应数据做点什么 (2xx范围内的状态码)
    console.log('响应拦截器 - 处理成功响应', response);
    return response.data; // 通常我们只需要data部分
  }, 
  function (error) {
    // 对响应错误做点什么 (超出2xx范围的状态码)
    console.log('响应拦截器 - 处理错误响应', error);
    if (error.response.status === 401) {
      // 处理未授权错误
      window.location.href = '/login';
    }
    return Promise.reject(error);
  }
);

2. 类封装写法(更工程化)

class HttpInterceptor {
  constructor() {
    this.instance = axios.create({
      baseURL: 'https://api.example.com',
      timeout: 10000
    });
    this.setupInterceptors();
  }
  
  setupInterceptors() {
    // 请求拦截
    this.instance.interceptors.request.use(
      config => {
        // 请求前添加loading
        this.showLoading();
        // 添加认证信息
        const token = localStorage.getItem('token');
        if (token) {
          config.headers.Authorization = `Bearer ${token}`;
        }
        return config;
      },
      error => {
        this.hideLoading();
        return Promise.reject(error);
      }
    );
    
    // 响应拦截
    this.instance.interceptors.response.use(
      response => {
        this.hideLoading();
        // 处理成功响应
        if (response.data.code === 200) {
          return response.data.data;
        } else {
          // 业务逻辑错误
          this.handleBusinessError(response.data);
          return Promise.reject(response.data);
        }
      },
      error => {
        this.hideLoading();
        // 处理HTTP错误
        this.handleHttpError(error);
        return Promise.reject(error);
      }
    );
  }
  
  showLoading() { /* 显示加载动画 */ }
  hideLoading() { /* 隐藏加载动画 */ }
  
  handleBusinessError(data) {
    const errorMap = {
      401: '未授权,请重新登录',
      403: '拒绝访问',
      404: '请求资源不存在',
      500: '服务器错误'
    };
    const message = errorMap[data.code] || '未知错误';
    alert(message);
  }
  
  handleHttpError(error) {
    if (error.response) {
      // 服务器返回了响应但状态码不在2xx范围
      console.error('响应错误:', error.response.status);
    } else if (error.request) {
      // 请求已发出但没有收到响应
      console.error('无响应:', error.request);
    } else {
      // 发送请求时出了点问题
      console.error('请求错误:', error.message);
    }
  }
}

// 使用示例
const http = new HttpInterceptor();
http.instance.get('/user/123');

3. 多实例不同拦截策略

// 创建两个不同的axios实例
const publicApi = axios.create({
  baseURL: 'https://api.example.com/public'
});

const privateApi = axios.create({
  baseURL: 'https://api.example.com/private'
});

// 公共API拦截器 - 简单处理
publicApi.interceptors.response.use(
  response => response.data,
  error => {
    console.error('公共API错误:', error);
    return Promise.reject(error);
  }
);

// 私有API拦截器 - 复杂处理
privateApi.interceptors.request.use(config => {
  const token = localStorage.getItem('token');
  if (!token) {
    throw new Error('无访问令牌');
  }
  config.headers.Authorization = `Bearer ${token}`;
  return config;
});

privateApi.interceptors.response.use(
  response => response.data,
  error => {
    if (error.response.status === 401) {
      // token过期,跳转登录
      window.location.href = '/login?expired=1';
    }
    return Promise.reject(error);
  }
);

高级应用场景

1. 重试机制

axios.interceptors.response.use(
  response => response,
  error => {
    const config = error.config;
    // 设置重试次数,默认为0
    config.__retryCount = config.__retryCount || 0;
    
    // 检查是否超过最大重试次数(3次)
    if (config.__retryCount >= 3) {
      return Promise.reject(error);
    }
    
    // 增加重试计数器
    config.__retryCount += 1;
    
    // 创建新的Promise来处理指数退避
    const delay = new Promise(resolve => {
      setTimeout(() => resolve(), 1000 * config.__retryCount);
    });
    
    // 返回Promise,在延迟后重试请求
    return delay.then(() => axios(config));
  }
);

2. 请求取消

// 创建一个Map来存储请求标识和取消函数
const pendingRequests = new Map();

// 生成请求标识
const generateReqKey = (config) => {
  const { method, url, params, data } = config;
  return [method, url, JSON.stringify(params), JSON.stringify(data)].join('&');
};

// 请求拦截器 - 添加取消逻辑
axios.interceptors.request.use(config => {
  const requestKey = generateReqKey(config);
  if (pendingRequests.has(requestKey)) {
    // 如果请求已存在,取消前一个
    pendingRequests.get(requestKey).cancel('重复请求');
  }
  // 创建取消令牌
  config.cancelToken = new axios.CancelToken(cancel => {
    pendingRequests.set(requestKey, { cancel });
  });
  return config;
});

// 响应拦截器 - 移除已完成请求
axios.interceptors.response.use(response => {
  const requestKey = generateReqKey(response.config);
  pendingRequests.delete(requestKey);
  return response;
}, error => {
  if (axios.isCancel(error)) {
    console.log('请求被取消:', error.message);
    return Promise.reject(error);
  }
  const requestKey = generateReqKey(error.config);
  if (error.config) {
    pendingRequests.delete(requestKey);
  }
  return Promise.reject(error);
});

3. 性能监控

// 请求开始时间记录
axios.interceptors.request.use(config => {
  config.metadata = { startTime: new Date() };
  return config;
});

// 响应时间计算
axios.interceptors.response.use(
  response => {
    const endTime = new Date();
    const duration = endTime - response.config.metadata.startTime;
    console.log(`请求 ${response.config.url} 耗时 ${duration}ms`);
    
    // 可以上报到性能监控系统
    reportPerformance({
      url: response.config.url,
      method: response.config.method,
      duration: duration,
      status: response.status
    });
    
    return response;
  },
  error => {
    if (error.config) {
      const endTime = new Date();
      const duration = endTime - error.config.metadata.startTime;
      console.error(`请求 ${error.config.url} 失败,耗时 ${duration}ms`);
      
      reportPerformance({
        url: error.config.url,
        method: error.config.method,
        duration: duration,
        status: error.response?.status || 0,
        error: error.message
      });
    }
    return Promise.reject(error);
  }
);

拦截器最佳实践

1. 组织架构建议

/src
  /api
    interceptors/
      request/
        auth.js       # 认证处理
        loading.js    # 加载状态
        log.js        # 请求日志
      response/
        error.js      # 错误处理
        format.js     # 数据格式化
      index.js        # 整合所有拦截器
    services/         # 各个API模块
    http.js           # axios实例创建

2. 代码示例:模块化拦截器

// http.js - 创建axios实例
import axios from 'axios';

const http = axios.create({
  baseURL: process.env.VUE_APP_API_BASE_URL,
  timeout: 30000
});

export default http;

// interceptors/request/auth.js
export default function addAuthToken(config) {
  const token = localStorage.getItem('token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
}

// interceptors/response/error.js
export default function handleError(error) {
  if (error.response) {
    switch (error.response.status) {
      case 400:
        console.error('请求错误');
        break;
      case 401:
        console.error('未授权,请重新登录');
        break;
      case 403:
        console.error('拒绝访问');
        break;
      case 404:
        console.error('请求资源不存在');
        break;
      case 500:
        console.error('服务器错误');
        break;
      default:
        console.error(`未知错误: ${error.response.status}`);
    }
  }
  return Promise.reject(error);
}

// interceptors/index.js - 整合拦截器
import http from '../../http';
import addAuthToken from './request/auth';
import handleError from './response/error';

// 请求拦截器
http.interceptors.request.use(addAuthToken);

// 响应拦截器
http.interceptors.response.use(
  response => response.data, // 直接返回data
  handleError
);

export default http;

3. 注意事项

  1. 执行顺序:多个拦截器按添加顺序执行

    • 请求拦截器:先添加的先执行
    • 响应拦截器:先添加的后执行
  2. 错误处理:确保拦截器中错误被正确传递

    • 使用Promise.reject传递错误
    • 不要"吞掉"错误
  3. 内存泄漏

    • 使用eject移除不再需要的拦截器
    const interceptor = axios.interceptors.request.use(...);
    axios.interceptors.request.eject(interceptor);
    
  4. 避免副作用

    • 不要在一个拦截器中修改另一个拦截器的行为
    • 保持拦截器职责单一
  5. 性能考虑

    • 拦截器中的逻辑应尽量轻量
    • 避免在拦截器中执行耗时操作

总结:拦截器的核心价值

Axios拦截器就像是你应用HTTP通信的"中间件管道",它提供了统一的入口和出口来处理所有请求和响应。通过合理使用拦截器,你可以:

  1. 标准化:统一处理认证、错误、loading状态等
  2. 解耦:将基础设施逻辑与业务逻辑分离
  3. 增强:添加重试、缓存、监控等高级功能
  4. 简化:减少业务代码中的重复逻辑

记住,强大的能力伴随着责任,拦截器虽然强大,但也需要合理设计和使用,避免过度复杂化你的请求处理流程。


网站公告

今日签到

点亮在社区的每一天
去签到