axios过滤重复请求及批量取消请求

实现思路:

把每次发送的请求及参数通过CancelToken创建标记添加到vuex的一个列表中,响应完成后从vuex的列表中删除该标记
每次添加标记到列表时先与列表比对是否重复,未重复正常添加,重复则不添加并取消该次请求。
通过循环vuex的请求标记列表,可以批量取消所有请求。

http.ts

该文件封装了axios的请求,对每次的请求通过cancel的createInterceptUrl方法创建标识,然后通过axiosPendingModule的addPending方法记录在vuex的列表中,响应结束后通过cancel的delayRepeatRequest从vuex中移除已经完成的请求

import axios from 'axios';
import qs from 'qs';
import { axiosPendingModule } from '@/store/modules/pending'; //请求连接状态列队
import { CancelToken, createInterceptUrl, removeRepeatRequest, delayRepeatRequest } from './cancel';

//ts接口
interface IParams {
    [key: string]: any;
}

interface IHttp {
    [key: string]: Function;
}

interface IStatusMessage {
    [key: number]: string;
}


/* 通用设置 */
axios.defaults.timeout = 1000 * 30; //请求超时设置,30秒超时
axios.defaults.baseURL = 'http://localhost:8080';

/* 请求拦截 */
axios.interceptors.request.use(
    (config: any) => {
        //在一个axios发送前执行一下取消重复请求操作
        removeRepeatRequest(config);

        /* 创建取消请求的token,并把本次请求的信息添加到请求列表 */
        config.cancelToken = new CancelToken((c) => {
            axiosPendingModule.addPending({
                u: createInterceptUrl(config),
                f: c,
            });
        });

        return config;
    },
    (error: any) => {
        return Promise.reject(error);
    }
);

//错误信息
let errorMessage: any = null;

/* 响应拦截 */
axios.interceptors.response.use(
    (response) => {
        //在一个axios响应后再执行一下取消操作,把已经完成的请求从pendingList中移除
        delayRepeatRequest(response.config);

        //状态码为200表示接口请求成功
        if (response.status === 200) {
            
            //code为0表示结果正常
            if (response.data && response.data.code === 0) {
                return Promise.resolve(response);
            } else {
                //异常状态码提示信息
                errorMessage && errorMessage.close();
                errorMessage = Message({
                    message: response.data.msg || 'Error',
                    type: 'error',
                    duration: 5 * 1000,
                });

                return Promise.reject(response);
            }
        } else {
            return Promise.reject(response);
        }
    },
    (error: any) => {
        if (error.response && error.response.status) {
        	let statusMessage: IStatusMessage = {
			    400: '错误请求',
			    401: '未授权,请重新登录',
			    403: '拒绝访问',
			    404: '请求错误,未找到该资源',
			    405: '请求方法未允许',
			    408: '请求超时',
			    500: '服务器端出错',
			    502: '网络错误',
			    503: '服务不可用',
			    504: '网络超时',
			    505: 'http版本不支持该请求',
			};
            error.message = statusMessage[error.response.status] || `连接错误${error.response.status}`;
        }
        return Promise.reject(error);
    }
);

/**
 * 公共请求
 * @param {String} type 请求类型
 * @param {String} url 请求的url地址
 * @param {Object} params 请求提交的参数
 */
function commonRequest(type: string, url: string, params: any = {}, config: any = { headers: { 'Content-Type': 'application/json' } }) {
    return new Promise((resolve, reject) => {
        let axiosParams: Array<any> = []; //axios方法请求的参数

        if (UserModule.token) {
            config.headers['Authorization'] = 'Bearer' + UserModule.token;
        }

        // get、delete共用请求
        if (type === 'get' || type === 'delete') {
            config[type === 'get' ? 'params' : 'data'] = params;
            axiosParams = [url, config];
        } else {
            // post、put、patch共用请求

            //不需要序列化参数的情况
            let notStringify = config.headers['Content-Type'] === 'application/json' || config.headers['Content-Type'] === 'multipart/form-data';

            //需要则qs序列化参数
            if (!notStringify) {
                params = qs.stringify(params, { arrayFormat: 'indices', allowDots: true });
            }
            axiosParams = [url, params, config];
        }
		
		
        (<IParams>axios)[type](...axiosParams).then(
            (res: any) => {
                resolve(res && res.data);
            },
            (err: any) => {
                reject(err);
            }
        );
    });
}

let http: IHttp = {};
let types: Array<string> = ['get', 'post', 'put', 'delete', 'patch'];

types.forEach((type: string) => {
    http[type] = (url: string, params: any, config: any) => {
        return commonRequest(type, url, params, config);
    };
});

export default http;

pending.ts

该文件用于对记录在vuex的请求列表的增、删以及执行取消请求动作

import { VuexModule, Module, Mutation, Action, getModule } from 'vuex-module-decorators';
import store from '@/store';

@Module({ name: 'axiosPending', dynamic: true, namespaced: true, store })
export default class AxiosPending extends VuexModule {
    public pendingList: any[] = [];

    get getPendingList() {
        return this.pendingList;
    }

    /* 添加请求到列表 */
    @Mutation
    private ADD_PENDING(pending: any) {
        this.pendingList.push(pending);
    }

    /* 从列表删除取消请求 */
    @Mutation
    private REMOVE_PENDING(pending: any) {
        debugger;
        let index = this.pendingList.indexOf(pending);
        index > -1 && this.pendingList.splice(index, 1);
        console.log(index);
        pending.f('取消请求'); //执行取消操作
    }

    /* 添加请求到列表 */
    @Action
    public addPending(request: any) {
        this.ADD_PENDING(request);
    }

    /* 从列表删除取消请求 */
    @Action
    public removePending(pending: any) {
        this.REMOVE_PENDING(pending);
    }

    /* 取消所有请求 */
    @Action
    public cancelAllPending() {
        for (let i = this.pendingList.length - 1; i >= 0; i--) {
            this.REMOVE_PENDING(this.pendingList[i]);
        }
    }
}

export const axiosPendingModule = getModule(AxiosPending);

cancel.ts

该文件封装了创建请求标识、移除重复请求、延期请求等一些常用的操作方法

import axios from 'axios';
import qs from 'qs';
import { axiosPendingModule } from '@/store/modules/pending';

/* 取消请求设置 */
export const CancelToken = axios.CancelToken;

/**
 * 创建用于拦截请求的标识,为保证请求信息完整,组合了请求的地址、类型、参数
 * @param {Object} config  axios请求的配置对象
 * @returns {String}  返回用于拦截的字符串标识
 */
export function createInterceptUrl(config: any) {
    //处理FormData不能直接被序列化的问题
    let dataStringify = '';
    //formdata特殊处理
    if (config.data instanceof FormData) {
        let file = config.data.get('file');
        dataStringify = qs.stringify(file);
    } else {
        dataStringify = qs.stringify(config.data);
    }
    let url = config.url + '&' + config.method + '&' + qs.stringify(config.params) + '&' + dataStringify;

    return url;
}

/**
 * 移除重复请求
 * @param {Object} config  请求配置对象
 */
export function removeRepeatRequest(config: any) {
    let currentPending = createInterceptUrl(config); //本次请求信息

    let pendingList = axiosPendingModule.getPendingList;
    pendingList.some((pending) => {
        //历史请求信息(请求的config.url中不包含baseUrl,但响应中的url会包含baseUrl,此处先处理两者差异)
        let historyPending = config.url.includes(axios.defaults.baseURL) ? axios.defaults.baseURL + pending.u : pending.u;

        if (currentPending === historyPending) {
            axiosPendingModule.removePending(pending);
        }
    });
}

/**
 * 延迟请求,相同请求不得在短时间内重复发送
 * @param {Object} config - 请求配置
 */
export function delayRepeatRequest(config: any) {
    setTimeout(() => {
        removeRepeatRequest(config);
    }, 500);
}

export default {
    CancelToken,
    createInterceptUrl,
    removeRepeatRequest,
    delayRepeatRequest,
};

取消所有请求应用示例

import { axiosPendingModule } from '@/store/modules/pending';

axiosPendingModule.cancelAllPending(); //取消所有请求
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章