vue3+ts 简单封装axios:实现错误重试、重复请求取消、手动取消

注意:需要安装crypto-js库

  1. utils\request.ts中:

    import axios from "axios";
    import type { InternalAxiosRequestConfig, AxiosResponse, AxiosInstance } from "axios";
    import { useRequeryStore } from "@/store/request";
    const useRequeryStoreInfo = useRequeryStore();
    
    let isLogoutShowing = false; // 登录过期弹窗锁
    
    // 创建Axios实例
    const axiosInstance: AxiosInstance = axios.create({
      baseURL: import.meta.env.VITE_BASE_URL,
      timeout: 60000 // 延时时间:60s
    });
    
    // 请求拦截器
    axiosInstance.interceptors.request.use(
      (config: InternalAxiosRequestConfig) => {
        // 添加token
        let token = useRequeryStoreInfo.getToken;
        if (token) config.headers["Authorization"] = "Bearer " + token;
    
        const key = useRequeryStoreInfo.getRequestKey(config);
        // 移除旧的请求
        useRequeryStoreInfo.removeOldRequest(key);
        // 开始新的请求
        const controller = new AbortController();
        config.signal = controller.signal;
        useRequeryStoreInfo.setRequest(key, controller);
        return config;
      },
      (error) => {
        return Promise.reject(error);
      }
    );
    
    // 响应拦截器
    axiosInstance.interceptors.response.use(
      (response: AxiosResponse) => {
        // 移除完成的请求
        useRequeryStoreInfo.removeNewRequest(response.config);
        // 错误处理
        if (response.data.code != 200) {
          responseError(response.data.code, response.data.message);
          return Promise.reject(response.data);
        }
    
        let config = response.config;
        console.log(`%c${config.baseURL}${config.url} %c${config.method?.toUpperCase()}`, "color:#409EFF; font-weight:bold;", "color:#67C23A; font-weight:bold;");
        console.log("- 请求参数", config.data, config.params);
        console.log("- 响应结果", response.data);
        return Promise.resolve(response.data);
      },
      async (error) => {
        let config = error.config;
        // 移除完成的请求、不包括主动取消
        if (error.code !== "ERR_CANCELED" && config) useRequeryStoreInfo.removeNewRequest(error.config);
        // 错误重试
        if (config.errorRetry) {
          const retryTotal: number = config.retryTotal ?? 3;
          const retryCount: number = config.retryCount ?? 0;
          if (retryCount < retryTotal) {
            ElMessage.warning(`请求失败,正在进行第${retryCount + 1}次尝试重新请求...`);
            config.retryCount = retryCount + 1;
            await new Promise((resolve) => setTimeout(resolve, 1000));
            const newController = new AbortController();
            config.signal = newController.signal;
            return axiosInstance(config);
          }
        }
        // 错误提示处理
        if (error.code !== "ERR_CANCELED") {
          let { message } = error;
          if (message == "Network Error") message = "连接异常,请稍后重试...";
          else if (message.includes("timeout")) message = "请求超时,请稍后重试...";
          ElMessage.error(message || "未知错误,请联系管理员!");
        }
        return Promise.reject(error);
      }
    );
    
    // 响应错误处理
    const responseError = (code: number, message: string) => {
      switch (code) {
        case 401:
          if (isLogoutShowing) return;
          isLogoutShowing = true;
          ElMessageBox.confirm("登录过期,是否重新登录?", "Warning", {
            confirmButtonText: "重新登录",
            cancelButtonText: "取消",
            type: "warning"
          })
            .then(() => {
              useRequeryStoreInfo.logout();
            })
            .finally(() => {
              isLogoutShowing = false;
            });
          break;
        default:
          ElMessage.error(message || "未知错误,请联系管理员!");
          break;
      }
    };
    export default axiosInstance;
    
  2. store\request.ts中:

    import { defineStore } from "pinia";
    import { MD5 } from "crypto-js";
    import router from "@/router";
    
    /**
     * 网络请求
     * requestList:网络请求
     */
    interface Config {
      method?: string;
      url?: string;
      data?: any;
      params?: any;
    }
    interface State {
      token: string;
      requestList: Map<string, AbortController>;
    }
    export const useRequeryStore = defineStore("requery", {
      persist: {
        pick: ["token"]
      },
      state: (): State => ({
        token: "",
        requestList: new Map()
      }),
      getters: {
        // 获取requestList
        getRequestList: (state) => state.requestList,
        // 获取Token
        getToken: (state) => state.token
      },
      actions: {
        // 重新登录
        logout() {
          router.push({ name: "login" });
          localStorage.clear();
          sessionStorage.clear();
        },
        // 设置Token
        setToken(token: string) {
          this.token = token;
        },
        /**
         * 获取请求key
         * @param {Config} config 需要method, data, url, params数据
         * @returns {string} key
         */
        getRequestKey(config: Config): string {
          const { method, url, data, params } = config;
          let newData = typeof data === "object" ? JSON.stringify(data) : data;
          let newParams = typeof params === "object" ? JSON.stringify(params) : params;
          return MD5([method, url, newParams, newData].join("&")).toString();
        },
        /**
         * 取消请求
         * @param {string} key 通过getRequestKey生成的key
         * @param {boolean} isMessage 是否需要提示
         * @param {boolean} message 提示内容
         */
        cancelRequest(key: string, isMessage: boolean = false, message: string = "请勿重复操作!") {
          if (this.getRequestList.has(key)) {
            const controller = this.requestList.get(key);
            if (controller) {
              controller.abort();
              this.requestList.delete(key);
            }
            if (isMessage) ElMessage.warning(message);
          }
        },
        // 存储请求数据
        setRequest(key: string, controller: AbortController) {
          this.requestList.set(key, controller);
        },
        // 移除旧的请求
        removeOldRequest(key: string) {
          this.cancelRequest(key, true);
        },
        // 移除完成的请求
        removeNewRequest(config: Config) {
          const key = this.getRequestKey(config);
          this.getRequestList.delete(key);
        },
        /**
         * 手动取消请求
         * @param {Config} config 需要method, data, url, params数据,请和请求的时候一模一样
         */
        manualCancelRequest(config: Config) {
          const key = this.getRequestKey(config);
          this.cancelRequest(key, true, "成功取消请求!");
        }
      }
    });
    
  3. 封装请求接口:在api\login.ts

    import request from "@/utils/request";
    
    // 登录
    export const getLogin = (data?: any) => {
      return request({
        url: "/user/login",
        method: "post",
        data
      });
    };
    
  4. 错误重复请求:在api\login.ts

    import request from "@/utils/request";
    
    // 登录
    export const getLogin = (data?: any) => {
      return request({
        url: "/user/login",
        method: "post",
        data,
        errorRetry:true,
        retryTotal:3,
      });
    };
    
posted @ 2025-11-07 15:35  小周同学~  阅读(1)  评论(0)    收藏  举报