封装Axios,实现对频繁重复请求的拦截

前端在做接口调用的时候,往往需要做按钮阻塞,即当前请求未返回,禁止再次发送。

以前常规的做法是请求后把按钮disabled,接口响应后再放开,这种做法需要一个一个去加开关,而且在给老工程做按钮阻塞时工作量繁重且容易漏。

本文利用 axios.CancelToken Api来对接口进行全局拦截,一劳永逸,且优雅。

首先封装Http请求类

http-request.ts

import axios, { AxiosInstance, AxiosRequestConfig } from "axios";
import md5 from "js-md5";
import { ElMessage } from "element-plus";

interface Interceptor {
  onFulfilled: any;
  onRejected: any;
}

/**
 * Http类,对axios的封装,用于发送http请求
 * 主要实现重复请求的拦截(当同一请求未返回而再次发送的情况)
 * @param {AxiosRequestConfig} config - axios基础配置
 * @param {Interceptor} reqInterceptor - 请求拦截器自定义方法
 * @param {Interceptor} resInterceptor - 响应拦截器自定义方法
 * @author Ywq
 * @date 2022/8/9 10:07
 */

class Http {
  private instance: AxiosInstance; // axios 实例
  private pending: any = {}; // 网络请求记录map结构
  private CancelToken = axios.CancelToken;

  constructor(config: AxiosRequestConfig, reqInterceptor: Interceptor, resInterceptor: Interceptor) {
    this.instance = axios.create(config);
    this.instance.interceptors.request.use(
        (config) => {
          // 通过请求url、method、params、data字段生成md5值
          const key = this.getMd5String(config);
          config.cancelToken = new this.CancelToken(c => {
            if (this.pending[key]) {
              // 上次接口未返回时走此逻辑
              if (Date.now() - this.pending[key] > 5000) {
                // 超过5s,删除对应的请求记录,重新发起请求,即使未返回
                delete this.pending[key];
              } else {
                // 同一请求未返回,5s内再次发送,取消发送
                c("repeated:" + config.url);
              }
            }
            // 记录当前的请求,已存在则更新时间戳
            this.pending[key] = Date.now();
          });
          const token = localStorage.getItem("token");
          if (token) {
            if (!config.params) {
              config.params = {
                "access_token": token
              };
            } else {
              config.params.access_token = token;
            }
          }
          if (reqInterceptor.onFulfilled) {
            return reqInterceptor.onFulfilled(config);
          }
          return config;
        },
        (err) => {
          let error = err;
          if (reqInterceptor.onRejected) {
            error = reqInterceptor.onRejected(err);
          }
          ElMessage.warning(error);
          return Promise.reject(error);
        }
    );
    this.instance.interceptors.response.use(
        (response) => {
          const key = this.getMd5String(response.config);
          if (this.pending[key]) {
            // 请求结束,删除对应的请求记录
            delete this.pending[key];
          }
          if (resInterceptor.onFulfilled) {
            return resInterceptor.onFulfilled(response);
          }
          return response;
        },
        (err) => {
          if (err.message.includes("repeated")) {
            // Toast("您的操作过于频繁,请稍后再试");
            return Promise.reject(err);
          }
          const key = this.getMd5String(err.config);
          if (this.pending[key]) {
            // 请求结束,删除对应的请求记录
            delete this.pending[key];
          }
          // 除repeated外报错逻辑
          let error = err;
          if (resInterceptor.onRejected) {
            error = resInterceptor.onRejected(err);
          }
          ElMessage.warning(error?.message || error);
          return Promise.reject(error); // 返回接口返回的错误信息
        }
    );
  }

  private getMd5String(config: AxiosRequestConfig): string {
    return md5(`${config.url}&${config.method}&${JSON.stringify(config.data)}&${JSON.stringify(config.params)}`);
  }

  get(url: string, params: any) {
    return this.instance.get(url, { params });
  }

  post(url: string, data: any, config: AxiosRequestConfig | undefined = undefined) {
    return this.instance.post(url, data, config);
  }
}

export default Http;

封装请求方法,此处实现了三个实例,分别对响应做出了不同程度的拆包,开发时按需取用

http.ts

import axios, { AxiosResponse } from "axios";
import { ElMessage } from "element-plus";
import Http from "@/utils/class/http-request";

const isPro = process.env.NODE_ENV === "production";
const baseURL = location.origin + (isPro ? process.env.VUE_APP_SERVER_DIR : "");
const options = {
  baseURL,
  timeout: 60000
};
/**
 * 用于未登录情况下跳入登录页
 * @code {number}状态码
 */
const errorLogic = (code: number): void => {
  if (/^8\d{2}$/.test(String(code))) {
    ElMessage.warning("程序验证已过期,请重新登录");
    const timer = setTimeout(() => {
      if (process.env.NODE_ENV === "production") {
        location.href = process.env.BASE_URL + "login";
      } else {
        location.href = "/login";
      }
      clearTimeout(timer);
    }, 2000);
  }
};

axios.defaults.headers["Content-Type"] = "application/x-www-form-urlencoded";
axios.defaults.withCredentials = true; // 让ajax携带cookie

const r = new Http(options, {
      onFulfilled: null,
      onRejected: null
    },
    {
      onFulfilled: (response: AxiosResponse) => {
        const { data: { code, message, data } } = response;
        if (code === 0 || code === 200) {
          return data;
        }
        ElMessage.warning(message);
        return Promise.reject(data);
      },
      onRejected: null
    }
);

const req = new Http(options, {
      onFulfilled: null,
      onRejected: null
    },
    {
      onFulfilled:
          (response: AxiosResponse) => {
            const { data } = response;
            const { code, message } = data;
            if (code === 0 || code === 200) {
              return data;
            }
            ElMessage.warning(message);
            return Promise.reject(data);
          },
      onRejected: null
    }
);

const request = new Http(options, {
      onFulfilled: null,
      onRejected: null
    },
    {
      onFulfilled: (response: any) => {
        const { data: { code, message } } = response;
        if (code === 0 || code === 200) {
          return response;
        }
        ElMessage.warning(message);
        return Promise.reject(response);
      },
      onRejected: null
    }
);

/**
 * r实例对应的响应
 */
type R<T> = Promise<AxiosResponse<T>>;

/**
 * req例对应的响应
 */
type Res<T> = Promise<AxiosResponse<{ code: number; message: string; data: T }>>;

/**
 * request实例对应的响应
 */
type Response<T> = Promise<AxiosResponse<Promise<AxiosResponse<T>>>>;

export { r, req, request, R, Res, Response };

// #utf8 编码否则会出现中文乱码
// 200=成功
// 0=成功
// 400=参数异常
// 404=资源不存在
// 401=需要认证后访问
// 403=系统拒绝访问
// 500=系统异常
// 504=请求超时
// 700=用户名不能为test
// 701=用户不存在
// 800=验证码不正确
// 801=不支持的登录类型
// 802=登录失败
// 803=账户名或者密码输入错误
// 804=账户被锁定,请联系管理员
// 805=密码过期,请联系管理员
// 806=账户过期,请联系管理员
// 807=账户被禁用,请联系管理员
// 808=账户没有登录
// 809=没有权限访问
// 813=未查到法院信息
// 814=不支持跨省查询
// 815=不支持自建查询

使用时

import { r, req, request, R, Res, Response } from "../http";

export interface ModelData {
  name: string;
  age: number;
}

export interface BaseParam {
  startDate: string; // 开始时间
  endDate: string; // 截止时间
  page: number; // 页码
  size: number; // 每页大小
}

export const getApi1 = (param: BaseParam): R<ModelData> => {
  return r.post("/xx", param);
};

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值