 闽公网安备 35020302035485号
                
                闽公网安备 35020302035485号
                当同样的请求还没返回结果再次请求直接取消
	
npm i axios element-plus在 src 下新建 http/request.ts 目录用于写我们的封装逻辑,然后调用 aixos 的 create 方法写一些基本配置
// 堆代码 duidaima.com
import axios, { AxiosResponse, InternalAxiosRequestConfig } from 'axios';
const service = axios.create({
  method: 'get',
  baseURL: import.meta.env.VITE_APP_API, //.env中的VITE_APP_API参数
  headers: {
    'Content-Type': 'application/json;charset=utf-8',
  },
  timeout: 10000, //超时时间
});
export default service;
这样便完成了 aixos 的基本配置,接下来我们可以在 http 下新建 api 目录用于存放我们接口请求,比如在 api 下创建 login.ts 用于写登录相关请求方法,这里的/auth/login我已经用 nestjs 写好了import request from './request';
export const login = (data: any) => {
  return request({
    url: '/auth/login',
    data,
    method: 'post',
  });
};
然后可以在页面进行调用<script lang="ts" setup>
import { login } from '@/http/login';
const loginManage = async () => {
  const data = await login({
    username: '堆代码',
    password: '1234',
  });
  console.log(data);
};
loginManage();
</script>
结果打印如下
service.interceptors.response.use(
  (res: AxiosResponse<any, any>) => {
    const { data } = res;
    if (data.code != 200) {
      ElMessage({
        message: data.describe,
        type: 'error',
      });
      if (data.code === 401) {
        //堆代码 duidaima.com
        //登录状态已过期.处理路由重定向
        console.log('loginOut');
      }
      throw new Error(data.describe);
    }
    return data;
  },
  (error) => {
    let { message } = error;
    if (message == 'Network Error') {
      message = '后端接口连接异常';
    } else if (message.includes('timeout')) {
      message = '系统接口请求超时';
    } else if (message.includes('Request failed with status code')) {
      message = '系统接口' + message.substr(message.length - 3) + '异常';
    }
    ElMessage({
      message: message,
      type: 'error',
    });
    return Promise.reject(error);
  },
);
这里规定后台 code 不是 200 的请求是异常的,需要弹出异常信息(当然这里由自己规定),同时 401 状态表示登录已过期,如果你需要更多的异常处理都可以写在这里。注意这里都是对 code 状态码的判断,这表示后台返回的 http 的 status 都是 2xx 才会进入的逻辑判断,如果后台返回 status 异常状态码比如 4xx,3xx 等就会进入 error 里,可以在 error 里进行逻辑处理,这里要和后端小朋友约定好service.interceptors.request.use(
  (config: InternalAxiosRequestConfig<any>) => {
    console.log(config);
    return config;
  },
  (error) => {
    console.log(error);
  },
);
我们可以看到 config 中包含了我们请求的一些信息像 headers,data 等等,我们是可以在这里对其进行修改的,比如我们在 headers 加一个 tokendeclare module "axios" {
  interface InternalAxiosRequestConfig<D = any, T = any> {
    isToken?: boolean;
  }
}
declare module "axios" {
  interface AxiosRequestConfig<D = any> {
    isToken?: boolean;
  }
}
service.interceptors.request.use(
  (config: InternalAxiosRequestConfig<any>) => {
    const { isToken = true } = config;
    if (localStorage.getItem('token') && !isToken) {
      config.headers['Authorization'] =
        'Bearer ' + localStorage.getItem('token'); // 让每个请求携带自定义token 请根据实际情况自行修改
    }
    return config;
  },
  (error) => {
    console.log(error);
  },
);
这里假设用户登录成功将 token 缓存到了 localStorage 中,接口是否需要 token 则是在请求的时候自己配置,比如 login 接口不加 token,注意这里需要给InternalAxiosRequestConfig和AxiosRequestConfig加上自定义的字段,否则 TS 会报错export const login = (data: any) => {
  return request({
    url: '/auth/login',
    data,
    isToken: false,
    method: 'post',
  });
};
此时我们可以获取到 config 中的 isToken 了。
	
let requestCount = 0;
const showLoading = () => {
  requestCount++;
  if (requestCount === 1) loadingInstance();
};
const closeLoading = () => {
  requestCount--;
  if (requestCount === 0) loadingInstance().close();
};
service.interceptors.request.use(
  (config: InternalAxiosRequestConfig<any>) => {
    const { loading = true, isToken = true } = config;
    return config;
  },
  (error) => {
    console.log(error);
  },
);
service.interceptors.response.use(
  (res: AxiosResponse<any, any>) => {
    const { data, config } = res;
    const { loading = true } = config;
    if (loading) closeLoading();
  },
  (error) => {
    closeLoading();
    return Promise.reject(error);
  },
);
取消重复请求service.interceptors.request.use(
  (config: InternalAxiosRequestConfig<any>) => {
    const controller = new AbortController();
    const { loading = true, isToken = true } = config;
    config.signal = controller.signal;
    controller.abort();
    return config;
  },
  (error) => {
    console.log(error);
  },
);
这里是将 controller 的 signal 赋值给 config 的 sigal,然后执行 controller 的 abort 函数即可取消请求,知道了如何取消 axios 请求,接下来我们就可以写取消重复请求的逻辑了。当拦截到请求的时候,将 config 中的 data,url 作为 key 值,AbortController 实例作为 value 存在一个 map 中,判断是否 key 值是否存在来决定是取消请求还是保存实例const requestMap = new Map();
service.interceptors.request.use(
  (config: InternalAxiosRequestConfig<any>) => {
    const controller = new AbortController();
    const key = config.data + config.url;
    config.signal = controller.signal;
    if (requestMap.has(key)) {
      requestMap.get(key).abort();
      requestMap.delete(key);
    } else {
      requestMap.set(key, controller);
    }
    return config;
  },
  (error) => {
    console.log(error);
  },
);
我们短时间内发送两次请求就会发现有一个请求被取消了。到这里基本就完成了 axios 的封装,下面是完整代码,直接 CV,就可以摸鱼一整天~mport axios, {
  AxiosInstance,
  AxiosResponse,
  InternalAxiosRequestConfig,
} from "axios";
import { ElMessage, ElLoading } from "element-plus";
const loadingInstance = ElLoading.service;
let requestCount = 0;
const showLoading = () => {
  requestCount++;
  if (requestCount === 1) loadingInstance();
};
const closeLoading = () => {
  requestCount--;
  if (requestCount === 0) loadingInstance().close();
};
const service: AxiosInstance = axios.create({
  method: "get",
  baseURL: import.meta.env.VITE_APP_API,
  headers: {
    "Content-Type": "application/json;charset=utf-8",
  },
  timeout: 10000,
});
//请求拦截
declare module "axios" {
  interface InternalAxiosRequestConfig<D = any, T = any> {
    loading?: boolean;
    isToken?: boolean;
  }
}
declare module "axios" {
  interface AxiosRequestConfig<D = any> {
    loading?: boolean;
    isToken?: boolean;
  }
}
const requestMap = new Map();
service.interceptors.request.use(
  (config: InternalAxiosRequestConfig<any>) => {
    const controller = new AbortController();
    const key = config.data + config.url;
    config.signal = controller.signal;
    if (requestMap.has(key)) {
      requestMap.get(key).abort();
      requestMap.delete(key);
    } else {
      requestMap.set(key, controller);
    }
    console.log(123);
    const { loading = true, isToken = true } = config;
    if (loading) showLoading();
    if (localStorage.getItem("token") && !isToken) {
      config.headers["Authorization"] =
        "Bearer " + localStorage.getItem("token"); // 让每个请求携带自定义token 请根据实际情况自行修改
    }
    return config;
  },
  (error) => {
    console.log(error);
  }
);
service.interceptors.response.use(
  (res: AxiosResponse<any, any>) => {
    const { data, config } = res;
    const { loading = true } = config;
    if (loading) closeLoading();
    if (data.code != 200) {
      ElMessage({
        message: data.describe,
        type: "error",
      });
      if (data.code === 401) {
        //登录状态已过期.处理路由重定向
        console.log("loginOut");
      }
      throw new Error(data.describe);
    }
    return data;
  },
  (error) => {
    closeLoading();
    let { message } = error;
    if (message == "Network Error") {
      message = "后端接口连接异常";
    } else if (message.includes("timeout")) {
      message = "系统接口请求超时";
    } else if (message.includes("Request failed with status code")) {
      message = "系统接口" + message.substr(message.length - 3) + "异常";
    }
    ElMessage({
      message: message,
      type: "error",
    });
    return Promise.reject(error);
  }
);
export default service;