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

本文介绍了一种在Vue.js项目中使用Vuex和axios来管理并取消重复HTTP请求的方法,通过创建请求标识、记录请求状态和批量取消请求,有效防止了网络请求的重复发送。
实现思路:

把每次发送的请求及参数通过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, cancelRepeatRequest, removePending, delayRemovePending } 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) => {
        //每次发送请求前执行过滤取消重复请求
        cancelRepeatRequest(config);

        /* 创建取消请求的token,并把本次请求的信息添加到请求列表 */
        config.cancelToken = new CancelToken((c) => {
            //请求的config.url中不包含baseUrl,但响应中的url会包含baseUrl,处理两者差异使信息保持一致
            let data = { ...config };
            data.url = axios.defaults.baseURL + data.url;

            axiosPendingModule.addPending({
                u: createInterceptUrl(data),
                f: c,
            });
        });

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

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

/* 响应拦截 */
axios.interceptors.response.use(
    (response) => {
        //延迟从列表删除请求标记,相同请求在500毫秒内重复发送将被请求拦截中的cancelRepeatRequest取消发送
        delayRemovePending(response.config, 500);

        //状态码为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) {
            removePending(error.response.config); //删除请求信息
    
	    	let statusMessage: IStatusMessage = {
			    400: '错误请求',
			    401: '未授权,请重新登录',
			    403: '拒绝访问',
			    404: '请求错误,未找到该资源',
			    405: '请求方法未允许',
			    408: '请求超时',
			    500: '服务器端出错',
			    502: '网络错误',
			    503: '服务不可用',
			    504: '网络超时',
			    505: 'http版本不支持该请求',
			};
		
	    	let msg = '网络错误';
            if (error.response.data) {
                msg = error.response.data.msg || '连接错误';
            } else if (error.response.status) {
                msg = statusMessage[error.response.status] || '连接错误';
            }    	    
            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 = {}) {
    let defaultConfig = {
        headers: { 'Content-Type': 'application/json' },
    };

    config = Object.assign({}, defaultConfig, config);

    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) {
        let index = this.pendingList.indexOf(pending);
        index > -1 && this.pendingList.splice(index, 1);
    }

    /* 从列表取消请求 */
    @Mutation
    private CANCEL_PENDING(pending: any) {
        pending.f('取消请求'); //执行取消操作
        //console.log('取消了:' + pending.u);
    }

    /* 添加请求到列表 */
    @Action
    public addPending(pending: any) {
        this.ADD_PENDING(pending);
        //console.log('新增了:' + pending.u);
    }

    /* 从列表删除请求 */
    @Action
    public removePending(pending: any) {
        this.REMOVE_PENDING(pending);
        //console.log('删除了:' + pending.u);
    }

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

    /* 取消所有请求 */
    @Action
    public cancelAllPending() {
        for (let i = this.pendingList.length - 1; i >= 0; i--) {
            this.CANCEL_PENDING(this.pendingList[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 {
        if (typeof config.data === 'object') {
            dataStringify = qs.stringify(config.data);
        } else if (typeof config.data === 'string') {
            dataStringify = qs.stringify(JSON.parse(config.data));
        } else {
            dataStringify = '';
        }
    }
    let url = config.url + '&' + config.method + '&' + qs.stringify(config.params) + '&' + dataStringify;

    return url;
}

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

    let pendingList = axiosPendingModule.getPendingList;
    pendingList.some((pending) => {
        let historyPending = pending.u;
        if (currentPending === historyPending) {
            console.log('取消重复请求');
            axiosPendingModule.cancelPending(pending);
        }
    });
}

/**
 * 移除响应完成后列表中对应的请求信息
 * @param {Object} config  请求配置对象
 */
export function removePending(config: any) {
    let currentPending = createInterceptUrl(config); //本次请求信息

    let pendingList = axiosPendingModule.getPendingList;
    pendingList.some((pending) => {
        let historyPending = pending.u;

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

/**
 * 延迟从列表删除请求标记,相同请求不得在短时间内重复发送
 * @param config - 请求对象
 * @param duration - 延时时长
 */
export function delayRemovePending(config: any, duration: number = 500) {
    setTimeout(() => {
        removePending(config);
    }, duration);
}

export default {
    CancelToken,
    createInterceptUrl,
    cancelRepeatRequest,
    delayRemovePending,
    removePending,
};


取消所有请求应用示例
import { axiosPendingModule } from '@/store/modules/pending';

axiosPendingModule.cancelAllPending(); //取消所有请求
在进行网络请求,数字数组通常作为请求参数的一部分传递给服务器。这类参数可以出现在 URL 的查询字符串中,也可以作为 POST 请求体的一部分。使用数字数组的 URL 通常用于向服务器传递多个数值参数,例如过滤条件、批量操作标识符等。 一种常见的场景是使用 `GET` 或 `POST` 请求传递数组参数。例如,若要请求一组特定 ID 的资源,URL 可能如下所示: ``` https://api.example.com/data?ids[]=101&ids[]=102&ids[]=103 ``` 或者在某些服务器端配置下,使用不带 `[]` 的形式: ``` https://api.example.com/data?ids=101&ids=102&ids=103 ``` 具体格式取决于后端如何解析参数。在 JavaScript 中,如果使用 `axios` 等库进行请求,可以通过 `qs` 模块来控制数组参数的序列化方式: ```javascript import axios from 'axios'; import qs from 'qs'; axios.get('https://api.example.com/data', { params: { ids: [101, 102, 103] }, paramsSerializer: params => { return qs.stringify(params, { arrayFormat: 'repeat' }); } }); ``` 上述代码会生成如下 URL: ``` https://api.example.com/data?ids=101&ids=102&ids=103 ``` 这种方式适用于需要传递多个数字值的场景,且可以根据服务器端的期望格式进行调整[^4]。 ### 使用数字数组的典型 URL 示例 1. **获取多个用户信息** ``` GET https://api.example.com/users?ids=1001&ids=1002&ids=1003 ``` 2. **删除多个事件** ``` DELETE https://api.example.com/event/batch/delete?ids=201&ids=202&ids=203 ``` 3. **查询多个订单状态** ``` GET https://api.example.com/orders/status?orderIds=9001&orderIds=9002 ``` 这些 URL 模式广泛应用于 RESTful API 设计中,以支持批量操作多数据查询需求[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值