axios++:防止重复提交全局统一拦截

防重提交是个老生常谈的问题,使用外部变量锁定或修改按钮状态的方式方式比较繁琐冗余,

而知乎的哥们在 怎样防止重复发送 Ajax 请求?的问答上,提到了防重提交的几个方式,

根据实际项目的需求,采用了A. 独占型提交 + D. 懒惰型提交组合方式,代码实现如下:

// http.js

import { debounce } from "../debounce";

let pendingArr = [];
let CancelToken = axios.CancelToken;
let pendingHandler = (flag, cancelFn) => {
    if (pendingArr.indexOf(flag) > -1) {
        if (cancelFn) {
            cancelFn(); // cancel
        } else {
            pendingArr.splice(pendingArr.indexOf(flag), 1); // remove flag
        }
    } else {
        if (cancelFn) {
            pendingArr.push(flag);
        }
    }
};
// request interceptor
axios.interceptors.request.use(
    config => {
        config.cancelToken = new CancelToken(cancelFn => {
            pendingHandler(config.baseURL + config.url + "&" + config.method, cancelFn);
        });
        return config;
    },
    err => {
        return Promise.reject(err);
    }
);

// response interceptor
axios.interceptors.response.use(
    response => {
        pendingHandler(response.config.url + "&" + response.config.method);
        return response;
    },
    err => {
        pendingArr = [];
        return Promise.reject(err);
    }
);

return debounce(
    axios(config)
    .then(response => {
        // handle response
        resolve(response.data)
    })
    .catch(thrown => {
        if (axios.isCancel(thrown)) {
            console.log("Request canceled", thrown.message);
        } else {
            let { response, request, message } = thrown;
            reject(message);
        }
    }),
    500,
    true
);
// debounce.js
export function debounce(func, wait, immediate) {
    var timeout, args, context, timestamp, result;
    if (null == wait) wait = 100;

    function later() {
        var last = Date.now() - timestamp;

        if (last < wait && last >= 0) {
            timeout = setTimeout(later, wait - last);
        } else {
            timeout = null;
            if (!immediate) {
                result = func.apply(context, args);
                context = args = null;
            }
        }
    }

    var debounced = function() {
        context = this;
        args = arguments;
        timestamp = Date.now();
        var callNow = immediate && !timeout;
        if (!timeout) timeout = setTimeout(later, wait);
        if (callNow) {
            result = func.apply(context, args);
            context = args = null;
        }

        return result;
    };

    debounced.clear = function() {
        if (timeout) {
            clearTimeout(timeout);
            timeout = null;
        }
    };

    debounced.flush = function() {
        if (timeout) {
            result = func.apply(context, args);
            context = args = null;

            clearTimeout(timeout);
            timeout = null;
        }
    };

    return debounced;
}

这里用到了 axios 拦截器,初始化一个pendingArr数组,用于存放正在 pending 的请求,以url+method为标志,

在 http response 拦截器删除完成 pending 的请求,在 http request 的拦截器中,先判断是否存在正在 pending 的同一个请求,若无则把请求标志位存入数组,若存在,则取消请求。

到这里还未结束,前面做的是终止请求,但是请求已经发出,短时间内频繁请求,会对服务器造成一定压力,

所以我这里用了一个debounce (防抖动),规定时间内把触发非常频繁的事件合并成一次执行,比如我这里是500ms 内,触发很频繁的请求都会合并成一次执行,避免用户疯狂点击,触发多次请求的情况。

至于debounce的实现,我这里是摘取underscore源码,至于原理可以参考Debouncing and Throttling Explained Through Examples,这里阐述了 debounce (防抖动)throttling(节流阀)的原理及其异同。

简化版:

let pending = []; //声明一个数组用于存储每个ajax请求的取消函数和ajax标识
let cancelToken = axios.CancelToken;
let removePending = (config) => {
    for(let p in pending){
        if(pending[p].u === config.url + '&' + config.method) { //当当前请求在数组中存在时执行函数体
            pending[p].f(); //执行取消操作
            pending.splice(p, 1); //把这条记录从数组中移除
        }
    }
}
 
//添加请求拦截器
axios.interceptors.request.use(config=>{
     removePending(config); //在一个ajax发送前执行一下取消操作
     config.cancelToken = new cancelToken((c)=>{
        // 这里的ajax标识我是用请求地址&请求方式拼接的字符串,当然你可以选择其他的一些方式
        pending.push({ u: config.url + '&' + config.method, f: c });  
    });
     return config;
   },error => {
     return Promise.reject(error);
   });
 
//添加响应拦截器
axios.interceptors.response.use(response=>{
      removePending(res.config);  //在一个ajax响应后再执行一下取消操作,把已经完成的请求从pending中移除
      return response;
   },error =>{
      return { data: { } }; 返回一个空对象,否则控制台报错
   });

 

引:https://www.cnblogs.com/Sabo-dudu/p/12457186.html

posted @ 2021-10-14 17:38  coding++  阅读(351)  评论(0编辑  收藏  举报