axios二次封装

233 阅读9分钟

创建一个二次封装的 Axios 库,该库能够自动处理 Token 刷新逻辑,同时具备取消请求的功能,并能够根据不同环境配置基础 URL 和其他配置。以下是实现这一目标的具体步骤:

思路步骤

1. 创建Axios实例

  • 目的:初始化Axios实例,并设置一些基本配置,如基础URL和超时时间。

  • 实现

    1. 导入axios库。
    2. 使用axios.create()方法创建一个新的Axios实例。
    3. 配置基础URL(通过环境变量动态设置)和请求超时时间。
    4. 导出创建的Axios实例。

2. 请求拦截器

  • 目的:在请求被发送到服务器之前对请求进行预处理,例如添加认证Token到请求头部。

  • 实现

    1. 使用axios.interceptors.request.use()方法添加请求拦截器。
    2. 拦截器函数接收配置对象作为参数。
    3. 从本地存储中获取认证Token。
    4. 如果存在Token,将其添加到请求头部的Authorization字段中。
    5. 返回修改后的配置对象。

3. 响应拦截器

  • 目的:在响应到达客户端后对其进行处理,例如处理HTTP状态码、错误状态,并实现Token刷新逻辑。

  • 实现

    1. 使用axios.interceptors.response.use()方法添加响应拦截器。
    2. 拦截器函数接收响应对象作为参数。
    3. 检查响应的状态码。
    4. 对于特定的状态码(如401),触发Token刷新流程。
    5. 对于其他状态码,输出相应的错误信息。
    6. 返回处理后的响应对象或者抛出错误。

4. Token刷新机制

  • 目的:当遇到401状态码时,自动刷新Token并重新发起请求。

  • 实现

    1. 定义一个refreshToken函数,用于处理Token刷新过程。
    2. 使用axios.post()向服务器发送刷新Token的请求。
    3. 成功刷新Token后,更新本地存储的Token和请求头部中的Token。
    4. 如果有正在刷新Token的请求正在进行,则等待该请求完成。
    5. 在响应拦截器中,当遇到401状态码时,调用refreshToken函数,并重新发送原始请求。

5. 取消重复请求

  • 目的:实现一个机制来管理正在进行的请求,并在发起相同请求时取消旧的请求。

  • 实现

    1. 使用Map来存储每个请求的取消源。
    2. 定义cancelRequest函数,用于取消指定的请求。
    3. 定义createCancelToken函数,用于创建新的取消请求源。
    4. 在请求拦截器中,为每个请求创建一个取消令牌。
    5. 在响应拦截器中,检查请求是否被取消,并相应地处理。

6. 超时重发

  • 目的:处理网络不稳定的情况,使用axios-retry插件实现自动重发。

  • 实现

    1. 导入axios-retry插件。
    2. 使用axios-retry(axios)函数启用重试功能。
    3. 配置重试次数和递增延迟时间。

7. 统一错误处理

  • 目的:创建一个统一的错误处理函数,用于处理所有API请求中的错误。

  • 实现

    1. 定义一个handleError函数,用于处理错误。
    2. 在请求失败时调用handleError函数。
    3. 输出错误信息,并返回一个被拒绝的Promise。

8. 提供便捷的API请求函数

  • 目的:提供一些便捷的方法来快速发起GET、POST等类型的请求。

  • 实现

    1. 定义一个通用的request函数,用于处理各种HTTP方法。
    2. 根据传入的HTTP方法名调用相应的axios方法。
    3. 捕获错误并调用handleError函数。
    4. 导出具体的方法如getpost等。

9. 环境变量配置

  • 目的:根据不同的部署环境动态配置基础URL和其他配置。

  • 实现

    1. 在环境变量配置文件中设置基础URL。
    2. 根据process.env.NODE_ENV值确定当前环境。
    3. 设置VUE_APP_API_BASE_URL环境变量,其值根据当前环境而定。

10. 导出封装后的Axios实例

  • 目的:导出封装后的Axios实例,以便在其他模块中使用。

  • 实现

    1. 导出创建的Axios实例。

使用示例 (MyComponent.vue)

  • 目的:在Vue组件中使用封装好的请求方法,并在组件销毁时取消所有未完成的请求。

  • 实现

    1. 在Vue组件中导入封装好的请求方法。
    2. 使用这些方法发起请求。
    3. beforeDestroy生命周期钩子中取消所有未完成的请求。

总结

通过以上步骤,我们创建了一个封装良好的Axios库,它具有以下特点:

  1. 自动处理Token刷新:当遇到401状态码时自动刷新Token,并重新发起请求。
  2. 取消重复请求:实现了取消重复请求的机制,避免不必要的网络请求。
  3. 超时重发:使用axios-retry插件处理网络不稳定的情况,实现自动重发功能。
  4. 环境变量配置:根据不同的部署环境动态配置基础URL和其他配置。
  5. 统一错误处理:创建了统一的错误处理函数,确保错误处理的一致性。
  6. 便捷的API请求函数:提供了便捷的方法来快速发起各种HTTP请求。
  7. 取消未完成的请求:在Vue组件中可以方便地取消未完成的请求

代码实现

1. 创建 Axios 实例

目的: 创建一个 Axios 实例,并设置基础配置。 实现:


import axios from 'axios';

const instance = axios.create({
  baseURL: process.env.VUE_APP_API_BASE_URL, // 动态配置基础 URL
  timeout: 5000, // 设置请求超时时间为 5 秒
});

export default instance;

2. 请求拦截器

目的: 在请求发送前添加认证 Token 到请求头部。

实现:


import axios from './axios-instance';

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

3. 响应拦截器

目的: 处理响应状态码,实现 Token 刷新逻辑。

实现:


// 响应拦截器
axios.interceptors.response.use(
  (response) => {
    return response.data;
  },
  (error) => {
    if (error.response) {
      switch (error.response.status) {
        case 401:
          // Token 过期,需要刷新 Token
          return refreshToken().then(() => {
            // 重新发送请求
            return axios(error.config);
          }).catch(() => {
            // Token 刷新失败
            return Promise.reject(error);
          });
        case 403:
          console.error('Forbidden:', error.response.data);
          break;
        case 404:
          console.error('Not Found:', error.response.data);
          break;
        case 500:
          console.error('Server Error:', error.response.data);
          break;
        default:
          console.error('Other Error:', error.response.data);
          break;
      }
    }
    return Promise.reject(error);
  }
);

4. Token 刷新机制

目的: 当遇到 401 状态码时,自动刷新 Token 并重新发起请求

实现:


// 存储刷新 Token 的请求
let refreshRequest;

/**
 * 刷新 Token 的函数
 * @returns {Promise}
 */
async function refreshToken() {
  // 如果已经有刷新 Token 的请求正在进行,则等待该请求完成
  if (refreshRequest) {
    return refreshRequest;
  }

  // 开始刷新 Token 的请求
  refreshRequest = axios.post('/auth/refresh-token').then((response) => {
    // 更新本地存储的 Token
    localStorage.setItem('token', response.data.token);
    // 更新请求头部中的 Token
    axios.defaults.headers.common['Authorization'] = `Bearer ${response.data.token}`;
    return response.data.token;
  }).finally(() => {
    // 清除刷新 Token 的请求
    refreshRequest = null;
  });

  return refreshRequest;
}

export default refreshToken;

5. 取消重复请求

目的: 实现机制来管理正在进行的请求,并在发起相同请求时取消旧的请求。

实现:

import axios from './axios-instance';
import CancelToken from 'axios/lib/cancel/CancelToken';
import isCancel from 'axios/lib/cancel/isCancel';

// 用于存储需要取消的请求
const cancelTokens = new Map();

/**
 * 取消指定请求
 * @param {String} requestName - 请求名称
 */
function cancelRequest(requestName) {
  if (cancelTokens.has(requestName)) {
    const source = cancelTokens.get(requestName);
    if (!source.cancelled) {
      source.cancel('取消请求');
    }
    cancelTokens.delete(requestName);
  }
}

/**
 * 创建取消请求源
 * @param {String} requestName - 请求名称
 * @returns {Object} - 取消请求源
 */
function createCancelToken(requestName) {
  const source = CancelToken.source();
  cancelTokens.set(requestName, source);
  return source;
}

// 更新请求配置以包含取消令牌
axios.interceptors.request.use(
  (config) => {
    config.cancelToken = createCancelToken(config.url);
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// 在响应拦截器中检查取消的请求
axios.interceptors.response.use(
  (response) => {
    return response;
  },
  (error) => {
    if (axios.isCancel(error)) {
      console.log('Request cancelled', error.message);
      return Promise.reject(error);
    }
    return Promise.reject(error);
  }
);

// 添加取消请求的方法
axios.cancelRequest = cancelRequest;

export default axios;

6. 超时重发

目的: 处理网络不稳定的情况,使用 axios-retry 插件实现自动重发。

实现:


import { retry } from 'axios-retry';

retry(axios, {
  retries: 3, // 重试次数
  retryDelay: (retryCount) => {
    return retryCount * 1000; // 递增延迟时间
  },
});

7. 统一错误处理

目的: 创建一个统一的错误处理函数,处理所有 API 请求中的错误。

实现:

/**
 * 统一错误处理函数
 * @param {Object} error - 错误对象
 */
 
function handleError(error) {
  console.error('Error:', error);
  return Promise.reject(error);
}

8. 提供便捷的 API 请求函数

目的: 提供一些便捷的方法来快速发起 GET、POST 等类型的请求。

实现:

/**
 * 封装请求函数
 * @param {String} method - HTTP 方法名
 * @param {String} url - 请求 URL
 * @param {Object} [data={}] - 请求体数据
 * @param {Object} [config={}] - 请求配置
 * @returns {Promise}
 */
async function request(method, url, data = {}, config = {}) {
  try {
    const response = await axios[method](url, data, config);
    return response;
  } catch (error) {
    return handleError(error);
  }
}

// 导出具体的方法
export const get = (url, config) => request('get', url, {}, config);
export const post = (url, data, config) => request('post', url, data, config);
export const put = (url, data, config) => request('put', url, data, config);
export const del = (url, data, config) => request('delete', url, data, config);

9. 环境变量配置 (environment.js)

目的: 根据不同的部署环境动态配置基础 URL 和其他配置。

实现:


// 设置环境变量
if (process.env.NODE_ENV === 'development') {
  process.env.VUE_APP_API_BASE_URL = 'http://localhost:3000/api';
} else if (process.env.NODE_ENV === 'production') {
  process.env.VUE_APP_API_BASE_URL = 'https://api.example.com';
}

10. 导出封装后的 Axios 实例

  • 目的:导出封装后的 Axios 实例,以便在其他模块中使用。

  • 实现

    1export default axios;
    

使用示例 (MyComponent.vue)

  • 目的:在 Vue 组件中使用封装好的请求方法,并在组件销毁时取消所有未完成的请求。
    export default axios;
  • 目的: 在 Vue 组件中使用封装好的请求方法,并在组件销毁时取消所有未完成的请求。

实现:

<template>
  <div>
    <button @click="fetchData">Fetch Data</button>
    <ul>
      <li v-for="(item, index) in items" :key="index">
        {{ item.title }}
      </li>
    </ul>
  </div>
</template>

<script>
import { get } from '@/services/request-helper';
import axios from '@/services/axios-instance';

export default {
  data() {
    return {
      items: [],
    };
  },

  methods: {
    async fetchData() {
      try {
        const data = await get('/items');
        this.items = data;
      } catch (error) {
        console.error('Failed to fetch data:', error);
      }
    },
  },

  beforeDestroy() {
    // 当组件销毁时取消所有未完成的请求
    axios.cancelRequest('/items');
  },
};
</script>

代码解释

  1. 创建 Axios 实例:

    • 设置基础 URL:通过环境变量动态设置基础 URL,这样可以根据不同的部署环境来调整。
    • 设置超时时间:设置了请求超时时间为 5 秒,这有助于提高用户体验,在长时间等待无响应的情况下给出反馈。
  2. 请求拦截器:

    • 添加认证 Token:在请求发送前,从本地存储中获取认证 Token,并将其添加到请求头部。
  3. 响应拦截器:

    • 处理不同状态码:针对不同的 HTTP 状态码进行相应的处理。
    • 实现 Token 刷新:当遇到 401 状态码时,自动刷新 Token 并重新发起请求。
  4. Token 刷新机制:

    • 刷新 Token:定义了一个 refreshToken 函数,用于刷新 Token。该函数确保同一时间内只有一个 Token 刷新请求正在进行。
    • 更新 Token:成功刷新后更新本地存储的 Token 和请求头部中的 Token。
  5. 取消重复请求:

    • 管理取消请求:使用 Map 来存储需要取消的请求,每个请求都有一个对应的取消源。
    • 取消指定请求:提供了 cancelRequest 方法来取消指定的请求。
    • 请求配置中包含取消令牌:在请求配置中添加取消令牌,这样可以在需要的时候取消请求。
  6. 超时重发:

    • 使用 axios-retry 插件:通过 axios-retry 插件实现请求的自动重发功能,这对于处理网络不稳定的情况非常有用。
  7. 统一错误处理:

    • 错误处理函数:定义了一个 handleError 函数,用于统一处理所有 API 请求中的错误。
  8. 提供便捷的 API 请求函数:

    • 封装请求函数:定义了一个通用的请求函数,可以处理 GET、POST、PUT 和 DELETE 请求。
    • 导出具体方法:提供了具体的请求方法,如 getpost 等,便于在应用中直接使用。
  9. 环境变量配置:

    • 动态配置基础 URL:根据当前环境设置基础 URL。
  10. 使用示例 (MyComponent.vue) :

  • Vue 组件:在 Vue 组件中使用封装好的请求方法,并在组件销毁时取消所有未完成的请求。