# axios 中取消请求及阻止重复请求的

  今天在掘金看到了一篇文章感觉写的非常的 nice 前端防御性编程,里面的内容写的非常的实用之前一直没弄的东西也弄弄完了。

  在实际项目中,我们可能需要对请求进行“防抖”处理。这里主要是为了阻止用户在某些情况下短时间内重复点击某个按钮,导致前端向后端重复发送多次请求,还有一种情况就是举个例子:几个 Tab 的切换但是下面的内容页用的是同一个,当网络情况差的时候切换的时候会出现后一个 Tab 渲染了前一个 Tab 请求的内容。

  为了防止这些发生,我们在 axios 请求的时候要做一些判断,用到的核心方法就是 axios 提供的 CancelToken

const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios
    .get("/user/12345", {
        // 必须对请求进行cancelToken设置
        cancelToken: source.token
    })
    .catch(function(thrown) {
        // 如果请求被取消则进入该方法判断
        if (axios.isCancel(thrown)) {
            console.log("Request canceled", thrown.message);
        } else {
            // handle error
        }
    });

// 取消上面的请求
// source.cancel('messge') message为可选项,必须为String
source.cancel("Operation canceled by the user.");
const CancelToken = axios.CancelToken;
let cancel;

axios.get("/user/12345", {
    // 在options中直接创建一个cancelToken对象
    cancelToken: new CancelToken(function executor(c) {
        cancel = c;
    })
});

// 取消上面的请求
cancel();

  上面是官方文档上的两个方法,然后我将方法修改了下能同时满足取消前面的全部请求,和取消相同请求。

// 创建一个 axios 实例
const service = axios.create({
    baseURL: process.env.VUE_APP_API,
    timeout: 5000 // 请求超时时间
});

// 存储的HTTP请求
let pending = [];
let cancelToekn = axios.CancelToken;
let removePending = (config, isAll) => {
    for (let i = 0; i < pending.length; i++) {
        if (isAll) {
            pending[i].fun();
            pending.splice(i, 1);
            i--;
        } else {
            // 通过记录的url, method和参数来判断请求是否是同一个
            if (pending[i].name == config.url + JSON.stringify(config.data) + "&" + config.method) {
                pending[i].fun();
                pending.splice(i, 1);
                break;
            }
        }
    }
};

// 请求拦截器
service.interceptors.request.use(
    config => {
        // 在HTTP请求前取消前面的所有请求(config.headers.removeP这个是自定义传参来确定这个请求是否需要取消前面所有的请求 通常用在渲染数据的接口)
        if (config.headers.removeP) removePending(config, true);
        // 在HTTP请求前取消相同的请求
        else removePending(config, false);

        // 记录本次HTTP请求
        config.cancelToken = new cancelToekn(c => {
            pending.push({
                name: config.url + JSON.stringify(config.data) + "&" + config.method,
                fun: c
            });
        });
        return config;
    },
    error => {
        // 发送失败
        console.log(error);
        return Promise.reject(error);
    }
);

// 响应拦截器
service.interceptors.response.use(
    response => {
        setTimeout(() => {
            // 请求成功后删除记录(延时是为了防止短时间内重复请求)
            removePending(response.config);
        }, 500);
    },
    error => {
        // 判断是否是被取消请求
        if (!axios.isCancel(error)) {
            setTimeout(() => {
                // 请求成功后删除记录(延时是为了防止短时间内重复请求)
                removePending(error.config);
            }, 500);
        }

        return Promise.reject(error);
    }
);