Axios配置取消重复请求(可配置、适配已有项目通用axios方法)🔥

3,307 阅读4分钟

哈喽,我是JL😄

共同成长是我一直以来的追求!✨

希望下面分享能让你有所收获💦

背景

日常开发中我们经常会用到搜索框进行搜索:

impicture_20220402_143530.png

当我们输入数字不断回车,或者不断点击旁边的搜索按钮,正常情况下会发送请求,或者其他场景如按钮点击等,在弱网情况情况下,或者接口卡的情况下,就会不断触发相同http请求,即可能导致的问题:

1. 重复http请求过多,造成性能浪费。
2. 可能出现当前搜搜结果返回之前请求的接口,导致数据错乱。

那么我们就需要配置取消重复请求,同时,往往我们需要在已有项目上配置,有可能出现改动成本大的情况,下面介绍一种比较少改动的方法,结合常规的取消重复请求配置,用最小成本去嵌入已有项目中。

常规解决方案:取消重复请求

定义: 完全相同的接口在上一个pending状态时,自动取消下一个请求。

Axios是一个强大的基于Promise的客户端,被广泛应用于各公司项目,这里主要讲Axios如何配置取消重复请求

1️⃣ abort

Axios 底层通过 XMLHttpRequest 对象来发起 HTTP 请求,该对象提供 abort方法来取消请求:

impicture_20220402_145501.png 当然,这种方法比较原始,我们来看另一种目前普遍流行的方法。

2️⃣ CancelToken

Axios内部是已经封装好另一个取消请求的方法,通过CancelToken达到取消请求的操作: impicture_20220402_150016.png

也可以通过传递一个 executor 函数到 CancelToken 的构造函数来创建一个 cancel token:

impicture_20220402_151233.png

知道了请求,接下来我们看一下怎么去配置化以及无缝接入已有项目通用Axios当中。

思路

1️⃣ 辅助函数 generateReqKey

需要一个存储方法generateReqKey,存储Map对象pendingRequest,根据当前请求信息,存储请求的唯一key值、以及取消函数。

唯一key值可指:请求方法、参数、地址,可通过qs进行序列化,增加点安全性~

function generateReqKey (config) { 
    const { method, url, params, data } = config
    return [method, url, Qs.stringify(params), Qs.stringify(data)].join("&")
}

2️⃣ 辅助函数 pendingRequest

需要一个pendingRequest方法,用于把当前请求信息添加到pendingRequest对象中,这里用到Axios取消方法CancelToken

const pendingRequest = new Map()
function addPendingRequest (config) {
    const requestKey = generateReqKey(config) // 配合上面generateReqKey方法
    config.cancelToken = config.cancelToken || new axios.CancelToken((cancel) => {
        if (!pendingRequest.has(requestKey)) {
            pendingRequest.set(requestKey, cancel)
        }
    })
}

3️⃣ 辅助函数 removePendingRequest

需要一个removePendingRequest方法,用来检查是否存在重复请求,若存在则取消已发请求并清除对应的存储。

function removePendingRequest (config) {
    const requestKey = generateReqKey(config) // 配合上面generateReqKey方法
    if (pendingRequest.has(requestKey)) {
        const cancelToken = pendingRequest.get(requestKey)
        cancelToken(requestKey)
        pendingRequest.delete(requestKey)
    }
}

4️⃣ 配置化

/**
 * @description axios实例配置化
 * @param {Boolean} reqCancel
 * @returns {any}
 */
const http = (userCancel = false) => {
    const service = axios.create({
       withCredentials: true, // 允许跨域携带cookie
       timeout: 30000
    })
    // request interceptor
    service.interceptors.request.use(config => {
        ... do some thing
        if (reqCancel) { // 重复请求处理
            removePendingRequest(config)
            addPendingRequest(config)
        }
        return config
    }, error => {
        return Promise.reject(error)
    })
    service.interceptors.response.use(response => {
        reqCancel && removePendiingRequest(response.config) // 移除重复请求
        ... do some thing
    }, error => {
        ... do some thing
    })
}

大部分逻辑沿用原先项目,只需要抽离出配置函数http,嵌入辅助函数1,2,3等。

5️⃣ 适配通用Axios

正常项目会配置axios,然后导出:

// axios.js
export default axios // 导出

// main.js
import axios from '@/util/axios' // 引入
...
Vue.prototype.$http = axios

// 页面上使用
...
this.$http.post()...

适配后,我们可以导出两种请求方式,一种通用,一种定制化

// axios.js
export const reqFree = http() // 通用
export const reqWithCancel = http(true) // 取消重复请求

// main.js
import { reqFree, reqWithCancel } from '@/util/axios'
...
Vue.prototype.$http = reqFree
Vue.prototype.$cancelHttp = reqWithCancel

页面上使用:
this.$http.post()...  // 通用不需要变动
this.$cancelHttp.post()... // 取消重复请求用法

通过这种配置,可以无缝嵌入到已有项目当中,不需要调整通用请求。

扩展

在项目开发中遇到另一种比较实用的方案,记录一下:

思路: 活用axios cancel token 取消重复请求,vuex或者pinia抛出方法存储对应请求的url以及cancel token以及判断是否存在重复请求,存在即调用对应url的cancel方法取消,这里通过数组存储映射关系。

// 以vuex为例
// store
const state = {
    cancelMap = []
}
...
const mutations = {
    SET_CANCEL_MAP: (state, { key, cancel }) => {
        const source = state.cancelMap.find(item => item.key === key)
        if (source) { // 存在重复请求
            source.source.cancel() // 取消对应请求
            state.cancelMap = state.cancelMap.filter(item => item.key !== key)
        }
        state.cancelMap.push({
            key,
            source: cancel
        })
    },
    DELETE_CANCEL_MAP: (state, key) => {
        state.cancelMap = state.cancelMap.filter(i => i.key !== key)
    }
}
...
const actions = {
    setCancelMap ({ commit }, { key, cancel }) {
        commit('SET_CANCEL_MAP', { key, cancel })
    },
    deleteCancelMap ({ commit }, { key }) {
        commit('DELETE_CANCEL_MAP', { key })
    },
}
...

// xxx.vue
import axios from 'axios'
import { mapState, mapActions } from 'vuex'

methods: {
    ...mapActions('xxx', ['setCancelMap']),
    getList () {
        const cancelToken = axios.CancelToken
        const source = cancelToken.source()
        const key = 'getList/'  // 这里用url作为键值
        this.setCancelMap({ key, cancel: source }) // 存储映射关系
        this.$http.post(key, {
           xxx: xxx
        }, { // get是第二个参数,post是第三个参数
            cancelToken: source.token
        }).catch(err => {
            // 加上判断,不然取消会执行这块逻辑
            if (axios.isCancel(err)) return
            // do some thing
        }).finally(() => {
            this.deleteCancelMap(key)
        })
    }
}

这种方式还可以延伸其他使用方法,例如切换菜单栏清除之前菜单栏未加载成功的请求等。

结语

希望能给你带来帮助✨~

分享不易,点赞鼓励咯🤞