揭秘Axios高效秘诀:一键拦截重复请求,让你的网络请求飞起来!

原理

通过 Aop 方式对 axiosadapter 进行拦截,把相同的请求除了第一个正常发起,其他重复的请求进行拦截挂起,等到第一个请求成功/失败之后再把结果返回给所有的重复请求。
axios配置

拦截规则

url请求方式请求参数 一致,则认为是重复请求。

实现

注意:本文使用的是最原始的 axios 进行创建的实例,如果对 axios 进行了二次封装的,请将 Axios.create(baseConfig) 替换为自己对应的创建 axios 实例的方法

import Axios from 'axios'

/**
 * @typedef {import('axios').AxiosRequestConfig} AxiosRequestConfig
 * @typedef {import('axios').AxiosInstance} AxiosInstance
 */

/* 缓存 */
const useCache = () => {
    const cacheMap = new Map()

    const addCache = (key, value) => cacheMap.set(key, value)

    const deleteCache = key => cacheMap.delete(key)

    const getCache = key => cacheMap.get(key)

    const hasCache = key => cacheMap.has(key)

    return {
        addCache,
        deleteCache,
        getCache,
        hasCache,
    }
}

/**
 * key规则
 * @param { AxiosRequestConfig } config
 * @return {`__${string}__${string}`}
 */
const getKeyByConfig = config => {
    const { url, data, method, params } = config
    // 根据请求方法选择参数
    const getStringParams = () => {
        try {
            if (method.toLowerCase() === 'get') {
                return encodeURIComponent(JSON.stringify(params))
            } else {
                return encodeURIComponent(JSON.stringify(data))
            }
        } catch (e) {
            console.error('params or data is not json format!', e)
            return ''
        }
    }
    return `__${method}__${encodeURIComponent(url)}__${getStringParams()}`
}
/**
 * 将filter进行标准化处理。
 * @param filter
 * @return {*|(function(*): (boolean|*))}
 */
const normalizeFilter = filter => {
    if (typeof filter === 'function') {
        return filter
    }
    return config => {
        if (!filter || (Array.isArray(filter) && !filter.length)) {
            return true
        }
        const { url } = config
        // 判断是否为正则
        if (filter instanceof RegExp) {
            return !filter.test(url)
        } else {
            const filterUrls = Array.isArray(filter) ? filter : [filter]
            return !filterUrls.includes(url)
        }
    }
}

/**
 * 接口拦截过滤
 * @param { AxiosRequestConfig } config 这个是每个接口的配置
 * @param { AxiosRequestConfig | undefined } baseConfig 这是基本配置
 */
const filterIntercept = (config, baseConfig) => {
    const { url, params, data } = config
    const { deduplicateFilter } = baseConfig ?? {}
    /* 没有url不处理  TODO: 对于 formData 暂不处理 */
    if (!url || params instanceof FormData || data instanceof FormData) {
        return false
    }
    if (deduplicateFilter) {
        return normalizeFilter(deduplicateFilter)(config)
    }
    /* other filter */
    return true
}

/**
 * 用于处理重复请求的适配器。对于重复请求会被挂起,直到第一个请求成功/失败后,会把结果给到其他重复请求。
 * @param { AxiosInstance } axiosInstance
 * @param { AxiosRequestConfig | undefined } baseConfig
 */
const deduplicateRequestsAdapter = (axiosInstance, baseConfig) => {
    const { addCache, deleteCache, getCache, hasCache } = useCache()

    /* 默认的适配器。用于调用原始的请求 */
    const defaultAdapter = axiosInstance.defaults.adapter

    if (!defaultAdapter) {
        return axiosInstance
    }

    /**
     * 通过aop方式修改默认适配器,进行重复请求拦截
     * @param { AxiosRequestConfig } config
     * @return {Promise<any>}
     */
    axiosInstance.defaults.adapter = async config => {
        if (!filterIntercept(config, baseConfig) || config.notDeduplicate) {
            return defaultAdapter(config)
        }
        const key = getKeyByConfig(config)
        /* 缓存存在则挂起 */
        if (hasCache(key)) {
            return new Promise((resolve, reject) => getCache(key).push({ resolve, reject }))
        } else {
            addCache(key, [])
        }
        let request = void 0
        try {
            request = await defaultAdapter(config)
            getCache(key).forEach(it => it.resolve(request))
        } catch (e) {
            /* TODO:针对错误是否进行重试 */
            getCache(key).forEach(it => it.reject(e))
        } finally {
            deleteCache(key)
        }
        return request
    }

    return axiosInstance
}

/**
 * 创建一个拦截重复请求的axios实例
 * @param { AxiosRequestConfig | undefined } baseConfig
 */
export const createDuplicateRequestAxios = baseConfig => {
    return deduplicateRequestsAdapter(Axios.create(baseConfig), baseConfig)
}

使用

const instance = createDuplicateRequestAxios()
instance
    .get('/api/resource', {
        params: {
            search: 'keyword',
            page: 2,
        },
    })
    .catch(res => {
        console.log(res, '111')
    })
instance
    .get('/api/resource', {
        params: {
            search: 'keyword',
            page: 2,
        },
    })
    .catch(res => {
        console.log(res, '222')
    })
instance
    .get('/api/resource', {
        params: {
            search: 'keyword',
            page: 2,
        },
    })
    .catch(res => {
        console.log(res, '333')
    })

instance
    .post('/api/resource', {
        data: {
            search: 'keyword',
            page: 2,
        },
    })
    .catch(res => {
        console.log(res, '444')
    })
instance
    .post('/api/resource', {
        data: {
            search: 'keyword',
            page: 2,
        },
    })
    .catch(res => {
        console.log(res, '555')
    })
instance
    .post('/api/resource', {
        data: {
            search: 'keyword',
            page: 2,
        },
    })
    .catch(res => {
        console.log(res, '666')
    })

结果

一、可以看到请求只发起了两次 在这里插入图片描述
二、结果输出了 6 次。(因为有 6个请求
在这里插入图片描述

如果想要部分或者某个接口不配置拦截重复请求

一、全局不使用

const instance = createDuplicateRequestAxios({ notDeduplicate: true })

二、部分请求不使用

const instance = createDuplicateRequestAxios({
	// 拦截单个请求
	deduplicateFilter: '/api/resource',
	// 拦截多个请求
	// deduplicateFilter: ['/api/resource', '/api/resource2', '/api/resource3'],
	// 通过正则匹配
	// deduplicateFilter: /^\api/,
})
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值