axios的个人封装使用

134 阅读6分钟

typescript版链接

1. 安装axios

npm install axios -S

2. 新增axios.js文件, 基本架子如下:

class HttpAxios {
  instance; // axios实例

  method = 'post'; // 请求方式

  timeout = 300 * 1000; // 超时时间


  constructor(config) {
    // 全局实例
    this.instance = axios.create(config);
  }

  /**
   * 发送请求
   * @param url 请求路径
   * @param params 请求参数
   * @param method 请求方式
   * @param config 更多的header配置,或请求配置,如是否显示loading等
   * @returns
   */
  sendRequest = (url, params, method = 'post', config) => {
    if (!this.instance) {
      return;
    }
    this.method = method;
    const _method = method.toLocaleLowerCase();
    if (_method === 'get') {
      return this.instance.get(url, { params });
    }
    // form形式提交
    let repData;
    if (_method === 'formdata') {
      reqData = new FormData();
      for (let key in params) {
        reqData.append(key, params[key]);
      }
    }
    return this.instance.post(url, repData || params);
  };
}

3. 新增请求拦截器

// 在构造函数中给实例instance添加请求拦截器
class HttpAxios {
  // ...
  constructor(config) {
    // ...
    // 设置请求拦截
    instance.interceptors.request.use(this._requestInterceptors, this._checkRequestError);

    instance.interceptors.response.use(this._responseInterceptors, this._checkResponseError);
  }
  /**
   * 请求拦截,处理header部分传值及其他配置
   * @param config
   */
  _requestInterceptors = (config) => {
    let _config = {
      withCredentials: false, // 处理跨域问题
      timeout: this.timeout // 超时时间
    };
    if (this.method == 'formate') {
      config.headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
    }
    config.headers = Object.assign({}, config.headers, _header);
    return Object.assign({}, config, _config);
  }

  /**
   * 处理请求错误时情况
   * @param error
   */
  _checkRequestError = (error) => {
    return Promise.reject(error);
  }
};

4. 新增响应拦截器

// 在构造函数中给实例instance添加响应拦截器
class HttpAxios {
  //...
  constructor(config) {
    //...
    // 设置响应拦截
    instance.interceptors.response.use(this._responseInterceptors, (error) => {
      return Promise.reject(error);
    });
  }

  /**
   * 返回拦截
   * @param response
   * @returns
   */
  _responseInterceptors = (response) => {
    let data = response.data || {};
    if (data.code === 200) {
      return data.data || '' ;
    }
    this._checkResponseCode(data);
    return undefined;
  }

  /**
   * 处理请求错误时情况 根据不同的状态码
   * @param error
   */
  _checkResponseError = (error) => {
    if (/timeout/g.test(error)) {
      return Promise.reject(error);
    }
    const { response } = error;
    const { status, statusText, data } = response || {};
    let msg = data?.message || CODE_MESSAGE[status] || statusText;
    return Promise.reject(error);
  };
}

5. 取消请求处理:给每个请求添加cancelToken,并在请求取消时取消请求,并新增配置,是否在切换页面时清除请求标记

1. 新增map对象,用于存储每个请求的标记
// 在构造函数中给实例instance添加响应拦截器
class HttpAxios {
  //...
  cancelTokenArr = []; // axios cancelToken 数组,方便处理取消请求

  clearFlag = true; // 是否在切换页面时清除请求标记
}
2. 在请求拦截中设置cancelToken
class HttpAxios {
  //...
  /**
   * 请求拦截,处理header部分传值及其他配置
   * @param config
   */
  _requestInterceptors = (config) => {
    // ...
    config.cancelToken = new axios.CancelToken((cancel) => {
      // 添加url,方便取消特定url的请求
      this.cancelTokenArr.push({ cancel, url: config.url });
    });
  } 
}
3. 添加清除函数
class HttpAxios {
  //...
  /**
   * 取消请求
   * @param url
   */
  async cancelRequest(url) {
    if (this.cancelTokenArr.length === 0) {
      return;
    }
    for (let i = 0; i < this.cancelTokenArr.length; i++) {
      if (this.cancelTokenArr[i].url === url) {
        this.cancelTokenArr[i].cancel();
        this.cancelTokenArr.splice(i, 1);
        break;
      }
    } 
  }
  /**
   * 清除所有请求
   */
  clearAllRequest = () => {
    this.cancelTokenArr.forEach((item) => {
      item.cancel();
    });
    this.cancelTokenArr = [];
  };
}
4. 例如在home.vue中使用
// home.vue
<script setup>
import HelloWorld from '@/components/HelloWorld.vue';
// 引入对应的httpAxios实例
import api, { httpAxios } from '@/api';
import { sleep } from '@/utils';
import demo from '@/api/demo';

const request = async () => {
  const res = await api.demo({msg: '123'});
  console.log(res);
}

request();
setTimeout(() => {
  console.log('setTimeout');
  // 取消请求
  httpAxios.cancelRequest(demo.demo.url);
}, 1000);
</script>

6. 添加失败重试机制

1. 修改返回拦截器,添加失败重试
class HttpAxios {
  //...
  _responseInterceptors = async (response) => {
    let data = response.data || {};
    if (data.code === 200) {
      return data.data || '' ;
    }
    await this._retryRequest(response.config)
    return undefined;
  };

  /**
   * 处理请求错误时情况 根据不同的状态码
   * @param error
   */
  _checkResponseError = async (error) => {
    await _retryRequest(error.config);
    if (/timeout/g.test(error)) {
      return Promise.reject(error);
    }
    // console.log(error);
    const { response } = error;
    const { status, statusText, data } = response || {};
    let msg = data?.message || CODE_MESSAGE[status] || statusText;
    return Promise.reject(error);
  };
  /**
   * 重试请求
   * @param config
   */
  _retryRequest = async (config) => {
    // 添加重试逻辑
    // 只有在有重试次数时,才回进行重试
    if (config && config.maxRetries && config.retryCount < config.maxRetries) {
      config.retryCount = (config.retryCount || 0) + 1;
      await sleep(config.retryDelay || 1000); // 重试前等待1秒
      return this.instance(config);
    }
  };
2. 在请求中添加配置
// demo.js
/**
 * 发送请求
 * @param url
 * @param params
 * @param method
 * @param config
 * @returns
 */
sendRequest = (url, params, method = 'post', config = {}) => {
  if (!this.instance) {
    return;
  }
  this.method = method;
  if (config?.maxRetries) {
    config.retryCount = 0;
  }
  const _method = method.toLocaleLowerCase();
  if (_method === 'get') {
    params = {
      params: params
    };
    return this.instance.get(url, {...params, ...config});
  }
  if (_method === 'formdata') {
    let reqData = new FormData();
    for (let key in params) {
      reqData.append(key, params[key]);
    }
    return this.instance.post(url, reqData, config);
  }
  return this.instance.post(url, params, config);
};

7. 其他:比如结合elementUI实现请求loading效果等

class HttpAxios {
  //...
  loadingInstance = null; // loading实例
  /**
   * 显示loading
   * @param config 请求配置
   */
  _showLoading(config) {
    if (config.showLoading && !this.loadingInstance) {
      this.loadingInstance = ElLoading.service({
        lock: true,
        text: config.loadingText || '加载中...',
        background: 'rgba(0, 0, 0, 0.7)'
      });
    }
    this.requestCount++;
  }
  /**
   * 隐藏loading
   */
  _hideLoading() {
    this.requestCount--;
    if (this.requestCount === 0 && this.loadingInstance) {
      this.loadingInstance.close();
      this.loadingInstance = null;
    }
  }
  /**
   * 请求拦截,处理header部分传值及其他配置
   * @param config
   */ 
  _requestInterceptors = (config) => {
    // 显示loading
    this._showLoading(config);
    let _config = {
      withCredentials: false, // 处理跨域问题
      timeout: this.timeout
    };
    let _header = {
    };
    if (this.method == 'formate') {
      _header['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
    }
    config.headers = Object.assign({}, config.headers, _header);
    config.cancelToken = new axios.CancelToken((cancel) => {
      // 添加url,方便取消特定url的请求
      this.cancelTokenArr.push({ cancel, url: config.url });
    });
    return Object.assign({}, config, _config);
  };

  /**
   * 返回拦截
   * @param response
   * @returns
   */
  _responseInterceptors = (response) => {
    // 隐藏loading
    // ...
    this._hideLoading();
  }
  /**
   * 处理请求错误时情况 根据不同的状态码
   * @param error
   */
  _checkResponseError = async (error) => {
    await this._retryRequest(error.config);
    // 隐藏loading
    this._hideLoading();
    if (/timeout/g.test(error)) {
      return Promise.reject(error);
    }
    // console.log(error);
    const { response } = error;
    const { status, statusText, data } = response || {};
    let msg = data?.message || CODE_MESSAGE[status] || statusText;
    return Promise.reject(error);
  };
}

8. 新增index.js导出实例

import { getHttpAxiosInstance } from "@/api/axios";
import demo from "@/api/demo";

export const httpAxios = getHttpAxiosInstance()();

function toMethod(options) {
  options.method = options.method || 'post';
  const { method = 'post', url, config } = options;
  return (param = {}, _config = {}) => {
    return httpAxios.sendRequest(url, param, method, {...config, _config});
  }
}
export function generateApiMap(maps) {
  let methodMap = {};
  for(let key in maps) {
    methodMap[key] = toMethod(maps[key]);
  }
  return methodMap;
}

export default {
  // 取出所有可遍历属性赋值在新的对象上
  ...generateApiMap({
    ...demo
  })
}

9. 完整的axios.js代码

import axios from 'axios';

const CODE_MESSAGE = {
  200: '服务器成功返回请求的数据。',
  201: '新建或修改数据成功。',
  202: '一个请求已经进入后台排队(异步任务)。',
  204: '删除数据成功。',
  400: '信息校验失败',
  401: '用户没有权限(令牌、用户名、密码错误)。',
  403: '用户得到授权,但是访问被禁止',
  404: '请求的资源不存在',
  406: '请求的格式不可得。',
  410: '请求的资源被永久删除,且不会再得到的。',
  422: '当创建一个对象时,发生一个验证错误。',
  500: '服务器发生错误,请检查服务器。',
  502: '网关错误。',
  503: '服务不可用,服务器暂时过载或维护。',
  504: '网关超时。'
};

class HttpAxios {
  instance; // axios实例

  method = 'post'; // 请求方式

  timeout = 300 * 1000; // 超时时间

  loadingInstance; // elementUI loading实例
  requestCount = 0; // 当前正在进行的请求计数器,用于控制loading的显示和隐藏

  cancelTokenArr = []; // axios cancelToken 数组,方便处理取消请求

  clearFlag = true; // 是否在切换页面时清除请求

  constructor(config) {
    let instance = axios.create(config);

    // 设置请求拦截
    instance.interceptors.request.use(this._requestInterceptors, (error) => {
      return Promise.reject(error);
    });

    instance.interceptors.response.use(this._responseInterceptors, this._checkResponseError);

    this.instance = instance;
  }

  /**
   * 显示loading
   * @param config 请求配置
   */
  _showLoading(config) {
    if (config.showLoading && !this.loadingInstance) {
      this.loadingInstance = ElLoading.service({
        lock: true,
        text: config.loadingText || '加载中...',
        background: 'rgba(0, 0, 0, 0.7)'
      });
    }
    this.requestCount++;
  }
  /**
   * 隐藏loading
   */
  _hideLoading() {
    this.requestCount--;
    if (this.requestCount === 0 && this.loadingInstance) {
      this.loadingInstance.close();
      this.loadingInstance = null;
    }
  }

  /**
   * 请求拦截,处理header部分传值及是否显示loading
   * @param config
   */
  _requestInterceptors = (config) => {
    // 显示loading
    this._showLoading(config);
    let _config = {
      withCredentials: false, // 处理跨域问题
      timeout: this.timeout
    };
    let _header = {
    };
    if (this.method == 'formate') {
      _header['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
    }
    config.headers = Object.assign({}, config.headers, _header);
    config.cancelToken = new axios.CancelToken((cancel) => {
      // 添加url,方便取消特定url的请求
      this.cancelTokenArr.push({ cancel, url: config.url });
    });
    return Object.assign({}, config, _config);
  };

  /**
   * 返回拦截
   * @param response
   * @returns
   */
  _responseInterceptors = async (response) => {
    let data = response.data || {};
    if (data.code === 200) {
      return data.data || '' ;
    }
    await this._retryRequest(response.config);
    this._hideLoading();
    const { code, message } = data;
    // TODO 弹出错误信息
    return undefined;
  };

  /**
   * 重试请求
   * @param config
   */
  _retryRequest = async (config) => {
    // 添加重试逻辑
    // 只有在有重试次数时,才回进行重试
    if (config && config.maxRetries && config.retryCount < config.maxRetries) {
      config.retryCount = (config.retryCount || 0) + 1;
      await sleep(config.retryDelay || 1000); // 重试前等待1秒
      return this.instance(config);
    }
  };

  /**
   * 处理请求错误时情况 根据不同的状态码
   * @param error
   */
  _checkResponseError = async (error) => {
    await this._retryRequest(error.config);
    this._hideLoading();
    if (/timeout/g.test(error)) {
      return Promise.reject(error);
    }
    // console.log(error);
    const { response } = error;
    const { status, statusText, data } = response || {};
    let msg = data?.message || CODE_MESSAGE[status] || statusText;
    return Promise.reject(error);
  };

  /**
   * 后台返回错误处理
   * @param code 后台定义错误码
   */
  _checkResponseCode = ({ code, message }) => {
    if (code === '401') {
      // TODO 处理登录失效问题
      sessionStorage.setItem('route_to_login', location.href);
      // router.push({ path: '/login' });
    }
  };

  /**
   * 发送请求
   * @param url
   * @param params
   * @param method
   * @param config
   * @returns
   */
  sendRequest = (url, params, method = 'post', config = {}) => {
    if (!this.instance) {
      return;
    }
    this.method = method;
    if (config?.maxRetries) {
      config.retryCount = 0;
    }
    const _method = method.toLocaleLowerCase();
    if (_method === 'get') {
      params = {
        params: params
      };
      return this.instance.get(url, {...params, ...config});
    }
    if (_method === 'formdata') {
      let reqData = new FormData();
      for (let key in params) {
        reqData.append(key, params[key]);
      }
      return this.instance.post(url, reqData, config);
    }
    return this.instance.post(url, params, config);
  };

  /**
   * 取消请求
   * @param url
   */
  async cancelRequest(url) {
    if (this.cancelTokenArr.length === 0) {
      return;
    }
    for (let i = 0; i < this.cancelTokenArr.length; i++) {
      if (this.cancelTokenArr[i].url === url) {
        this.cancelTokenArr[i].cancel();
        this.cancelTokenArr.splice(i, 1);
        break;
      }
    } 
  }
  /**
   * 清除axios 请求
   */
  async clearRequests() {
    if (this.cancelTokenArr.length === 0 || !this.clearFlag) {
      return;
    }
    this.cancelTokenArr.forEach((ele) => {
      ele.cancel();
    });
    this.cancelTokenArr = [];
  }
}
export const getHttpAxiosInstance = () => {
  let instance = null;
  return () => {
    if (!instance) {
      instance = new HttpAxios({}); 
    }
    return instance;
  }
}