Axios 实战:拦截器、封装与工作中的关键应用

在现代前端开发中,HTTP 请求是与后端服务交互的核心。一个功能全面、高度可扩展的请求模块可以显著提升开发效率和代码的可维护性。本文将从零开始,使用 Axios 封装一个灵活的通用请求模块,涵盖 请求基础动态参数处理错误处理上传文件请求取消 等功能。

axios 官网中文文档指南

为什么选择 Axios 而不是 Fetch?

虽然 Fetch 是浏览器内置的原生 API,但它在实际开发中的使用存在一些局限性。以下是 Axios 相较于 Fetch 的主要优势:

功能点 Axios Fetch
浏览器兼容性 支持 IE 和旧版浏览器 不支持 IE,需引入 Polyfill
请求拦截器 内置支持,便于统一处理请求逻辑 需要手动封装拦截器
响应拦截器 内置支持,便于统一处理响应逻辑 需要手动封装拦截器
错误处理 自动将 HTTP 错误状态码转为 catch 捕获 必须手动检查 response.ok
自动序列化 JSON 请求体自动序列化,响应体自动解析为对象 需要手动调用 JSON.stringifyJSON.parse
请求取消功能 内置 CancelToken 功能 原生不支持
超时配置 内置支持 必须通过 AbortController 手动实现
文件上传 内置支持 FormData 需手动设置 Content-Type

从这些对比可以看出,Axios 在开发体验和功能的便利性上更胜一筹,尤其适合需要统一管理请求逻辑、错误处理、以及复杂场景(如文件上传和请求取消)的项目。


安装 Axios

在项目中使用 Axios 前,需要先安装它。以下是安装 Axios 的方法:

1. 使用 npm 安装

npm install axios

2. 使用 yarn 安装

yarn add axios

封装目标

我们希望封装的请求模块具备以下功能:

  1. 支持多种请求方法:如 GETPOSTPUTDELETE 等。
  2. 动态参数处理:支持 params(查询参数)和 data(请求体数据)。
  3. 上传文件功能:支持通过 FormData 上传文件。
  4. 请求取消功能:防止重复请求,或在组件卸载时取消未完成的请求。
  5. 请求超时控制:可自定义超时时间。
  6. 失败自动重试:支持自定义重试次数和间隔。
  7. 全局 Loading 支持:统一展示和隐藏加载动画。
  8. 统一错误处理:集中处理常见错误,如鉴权失败(401)、服务器错误(500)等。

完整代码实现

以下是封装的 Axios 请求模块的完整代码。

1. 创建 Axios 实例

创建一个 Axios 实例,并设置基础配置:

import axios from 'axios';

// 根据环境动态设置 baseURL
const baseURL =
  process.env.NODE_ENV === 'production'
    ? 'https://api.production.com'
    : 'http://localhost:3000';

// 创建 Axios 实例
const axiosInstance = axios.create({
  baseURL, // 基础 API 地址
  timeout: 10000, // 超时时间设置(毫秒)
  withCredentials: true, // 跨域时携带 cookie
  headers: {
    'Content-Type': 'application/json', // 默认请求头
  },
  responseType: 'json', // 默认响应类型
});

export default axiosInstance;

2. 请求取消功能

防止重复请求,或在组件卸载时取消未完成的请求:

const CancelToken = axios.CancelToken;
const pendingRequests = new Map(); // 存储未完成的请求

/**
 * 添加请求到 pendingRequests 中
 */
const addPendingRequest = (config) => {
  const requestKey = `${config.method}:${config.url}:${JSON.stringify(config.params)}:${JSON.stringify(config.data)}`;
  if (!pendingRequests.has(requestKey)) {
    config.cancelToken = new CancelToken((cancel) => {
      pendingRequests.set(requestKey, cancel);
    });
  }
};

/**
 * 移除请求
 */
const removePendingRequest = (config) => {
  const requestKey = `${config.method}:${config.url}:${JSON.stringify(config.params)}:${JSON.stringify(config.data)}`;
  if (pendingRequests.has(requestKey)) {
    const cancel = pendingRequests.get(requestKey);
    cancel(`请求已取消:${requestKey}`); // 执行取消操作
    pendingRequests.delete(requestKey);
  }
};

/**
 * 清空所有请求
 */
const clearPendingRequests = () => {
  pendingRequests.forEach((cancel) => cancel());
  pendingRequests.clear();
};

可以在需要取消请求的场景(如组件卸载时)调用 clearPendingRequests()

3. 上传文件功能

支持通过 FormData 上传文件

/**
 * 上传文件
 * @param {string} url 上传文件的接口地址
 * @param {Object} files 文件数据对象(支持多个文件)
 * @param {Object} [extraData] 额外的表单数据
 * @param {Object} [headers={}] 自定义请求头
 * @returns {Promise} 返回一个 Promise
 */
const uploadFile = (url, files, extraData = {}, headers = {}) => {
  const formData = new FormData();

  // 将文件和额外数据添加到 FormData
  Object.keys(files).forEach((key) => {
    formData.append(key, files[key]);
  });

  Object.keys(extraData).forEach((key) => {
    formData.append(key, extraData[key]);
  });

  return axiosInstance.post(url, formData, {
    headers: {
      'Content-Type': 'multipart/form-data',
      ...headers,
    },
  });
};

4. 通用请求方法封装

支持动态参数、错误处理、失败重试等功能:

/**
 * 通用请求方法封装
 * @param {Object} options 请求配置选项
 * @param {string} options.url 请求地址
 * @param {string} [options.method='GET'] 请求方法
 * @param {Object} [options.params={}] 查询字符串参数
 * @param {Object} [options.data={}] 请求体数据
 * @param {Object} [options.headers={}] 自定义请求头
 * @param {boolean} [options.showLoading=false] 是否显示全局 Loading
 * @param {number} [options.retries=0] 请求失败时的重试次数
 * @param {number} [options.retryDelay=1000] 重试间隔时间(毫秒)
 * @param {number} [options.timeout=10000] 请求超时时间(毫秒)
 * @returns {Promise} 返回一个 Promise
 */
const request = async ({
  url,
  method = 'GET',
  params = {},
  data = {},
  headers = {},
  showLoading = false,
  retries = 0,
  retryDelay = 1000,
  timeout = 10000,
}) => {
  const config = { url, method, params, data, headers, timeout };

  const sendRequest = async (retryCount) => {
    try {
      removePendingRequest(config); // 防止重复请求
      addPendingRequest(config); // 添加当前请求

      const response = await axiosInstance(config);
      return response.data;
    } catch (error) {
      if (retryCount > 0) {
        await new Promise((resolve) => setTimeout(resolve, retryDelay));
        return sendRequest(retryCount - 1); // 递归重试
      }
      throw error;
    } finally {
      removePendingRequest(config); // 请求完成后移除
    }
  };

  if (showLoading) showGlobalLoading(); // 显示 Loading
  try {
    return await sendRequest(retries);
  } finally {
    if (showLoading) hideGlobalLoading(); // 隐藏 Loading
  }
};

export { request, uploadFile, clearPendingRequests };

5. 请求拦截器

在请求发送前,添加拦截器来实现以下功能:

  • 动态添加 Token:从本地存储中读取 token,并添加到请求头中。
  • 开启全局 Loading:通过某种机制触发全局加载动画。
axiosInstance.interceptors.request.use(
  (config) => {
    // 动态添加 Token
    const token = localStorage.getItem('token');
    if (token) {
      config.headers['Authorization'] = `Bearer ${token}`;
    }

    // 开启全局 Loading
    if (config.showLoading) {
      showGlobalLoading(); // 假设有全局 Loading 的控制函数
    }

    return config;
  },
  (error) => {
    // 请求错误处理
    return Promise.reject(error);
  }
);

6. 响应拦截器

在响应阶段,统一处理以下情况:

  • 请求成功:解析响应数据,并根据业务状态码处理逻辑。
  • 请求失败:如 401 未登录、403 无权限、500 服务器错误等。
axiosInstance.interceptors.response.use(
  (response) => {
    // 隐藏全局 Loading
    hideGlobalLoading(); // 假设有全局 Loading 的控制函数

    // 根据业务状态码处理逻辑
    const { code, message, data } = response.data;
    if (code === 200) {
      // 请求成功
      return data;
    } else {
      // 业务错误
      console.error(`业务错误:${message}`);
      return Promise.reject(new Error(message));
    }
  },
  (error) => {
    // 隐藏全局 Loading
    hideGlobalLoading();

    // 统一处理 HTTP 错误状态码
    if (error.response) {
      const { status } = error.response;
      switch (status) {
        case 401:
          console.error('未登录,请重新登录');
          // 跳转到登录页面
          break;
        case 403:
          console.error('无权限访问');
          break;
        case 500:
          console.error('服务器错误,请稍后重试');
          break;
        default:
          console.error('请求失败');
      }
    }
    return Promise.reject(error);
  }
);

使用示例

1. 发起 GET 请求
import { get } from './request';

get('/api/users', { page: 1, size: 10 }, true)
  .then((data) => console.log('成功:', data))
  .catch((error) => console.error('失败:', error));
2. 发起 POST 请求
import { post } from './request';

post('/api/login', { username: 'admin', password: '123456' }, true)
  .then((data) => console.log('登录成功:', data))
  .catch((error) => console.error('登录失败:', error));

总结

通过对 Axios 请求模块的封装,我们成功实现了一个功能全面、灵活易用的通用请求工具。该工具不仅满足了常见的开发需求,还具备了高扩展性和可维护性。其核心功能包括:

  1. 多种请求方法支持,满足常见的 GETPOSTPUTDELETE 等请求场景。
  2. 动态参数和文件上传支持,灵活适配复杂的请求需求。
  3. 请求取消功能,有效避免重复请求,同时在组件卸载时清理未完成的请求。
  4. 失败自动重试机制,通过配置重试次数和间隔,提升请求的稳定性。
  5. 全局 Loading 管理,为用户提供统一的加载动画体验。
  6. 统一错误处理,集中处理 HTTP 错误和业务状态码,减少重复代码。
  7. 请求超时控制,有效防止长时间无响应的请求占用资源。

在实际开发中,这个封装模块可以极大地提升代码复用性和团队协作效率,同时减少手动处理请求的复杂度。如果有更多个性化需求,也可以在此基础上进一步扩展,如支持更多的请求级功能(例如请求优先级、队列管理等)。

通过这样的封装,我们不仅解决了开发中的常见痛点,也为后续的功能扩展提供了强大的基础。希望这份封装工具能够帮助您更高效地完成项目开发!

你可能感兴趣的:(ajax,前端)