前端接口防重复请求

129 阅读2分钟

背景

今天发现系统中某些页面存在一个接口请求多次的情况,虽然是在写业务代码的时候导致了重复调用,但是由此觉得应该在axios请求的时候,加入接口重复请求取消的设计。

方案

经过调研发现了几种解决接口调用重复的方案

  • 利用防抖、截流的思想,在一段时间内针对请求只发一次,从交互层面防止。
  • 封装loading,发现有请求的时候直接loading,请求结束后再closeloading,从交互层面防止。
  • 在axios请求中封装,使用内部提供CancelToken来取消请求,但是在v0.22.0中已经被废弃,推荐使用AbortController来取消请求

实现

前两种实现就不说了,实现的主要就是第三种。

1.第一种可以使用一些第三方库来实现,比如点击某个按钮,一段时间之后再发出去请求,lodash库的debounce函数,就自己可以不用封装防抖函数了。

2.在有接口请求的地方开启loading,请求结束以后closeloading。或者可以在axios中开启一个全局loading,然后通过计数的方式,开启一个请求变量+1,请完成变量-1,等变量为0再closeLoading。

3.在axios中使用abortController.abort()来取消请求。

const pendingMap = new Map(); // 用来存放url的值
/**
 * 生成每个请求唯一的键
 * @param {*} config 
 * @returns string
 */
function getPendingUrl(config) {
    let { url, method, params, data } = config;
    if (typeof data === 'string') data = JSON.parse(data); // response里面返回的config.data是个字符串对象
    return [url, method, JSON.stringify(params), JSON.stringify(data)].join('&');
}

/**
 * 取消请求
 * @param {*} config 
 */
function removePending(config) {
    const url = getPendingUrl(config);
    if (pendingMap.has(url)) {
        // 如果当前请求在等待中,取消它并将其从等待中移除
        const abortController = pendingMap.get(url);
        if (abortController) {
            abortController.abort(url);
        }
        pendingMap.delete(url);
    }
}

/**
 * 添加请求到等待中
 * @param {*} config 
 */
function addPending(config) {
    removePending(config);
    const url = getPendingUrl(config);
    const controller = new AbortController();
    config.signal = config.signal || controller.signal;
    if (!pendingMap.has(url)) {
        // 如果当前请求不在等待中,将其添加到等待中
        pendingMap.set(url, controller);
    }
}

/**
 * 清除所有请求
 */

function removeAllPending() {
    pendingMap.forEach((abortController) => {
        if (abortController) {
            abortController.abort();
        }
    });
    clear();
}

/**
 * 清空队列
 */
function clear() {
    pendingMap.clear();
}

在请求拦截器中:
request.interceptors.request.use(
    (config) => {
        addPending(config);
        return config;
    },
    (error) => Promise.reject(error)
);
在响应拦截器中:
request.interceptors.response.use(
    (response) => {
        response && removePending(response.config);
        ....
        return response;
    },
    (error) => {
        response && removePending(response.config);
        if (error.name === 'CanceledError') {
            console.log("取消的请求")
        }
    }
);

写一个测试用例,打开控制台网络:

image.png

测试OK啦!!!

参考了vue-vben-admin的源码!

自己测试之后是能用的,有问题的话请大佬们指出,感恩!