axios 撤销请求

1,359 阅读2分钟

使用场景

  1. tab 切换时刷新列表数据,如果使用的是同一个 dom 展示数据,当请求有延时,可能会导致两个 tab 数据错乱
  2. 导出文件或下载文件时,想中断导出或下载

注意事项

前端主动撤销请求依旧会触发服务端逻辑,会占用服务端资源,一般按钮触发的请求用限制点击来处理

具体使用

主要思路:使用变量存储当前状态为请求状态的请求,在请求拦截中收集请求并判断是否有已有相同的正在请求过程中的请求,在响应拦截中移除该请求保证变量中存储的请求状态都为请求中

1.定义变量和取消方法
const httpPending = [] // 用于存储每个ajax请求的取消函数和ajax标识

// 取消请求方法
const cancelHttp = (name, config = {}) => {
    httpPending.forEach((e, i) => {
        if (e.n === name || e.n === config.xhrName) {
            // 当前请求在数组中存在时执行函数体
            e.f("我撤销了请求") // 执行取消操作
            httpPending.splice(i, 1) // 把这条记录从数组中移除
        }
    })
}
2.http 封装
export default class Http {
    static async request(method, url, opts = {}) {
        let params = {
            xhrName: (opts && opts.name) || "",
            method,
            url: uri,
        }
        return axios(params)
    }
    static post(url, opts) {
        return this.request("POST", url, opts)
    }
}
3.请求拦截
axios.interceptors.request.use(
    (config) => {
        // 取消上一次未完成的相同请求,注意项目中是否存在风险
        cancelHttp(null, config)
        config.cancelToken = new CancelToken((c) => {
            if (config.xhrName) {
                httpPending.push({
                    n: config.xhrName,
                    u: `${config.url}&${config.method}`,
                    f: c,
                })
            }
        })

        return config
    },
    (error) => Promise.reject(error)
)
4.响应拦截
axios.interceptors.response.use((res) => {
    cancelHttp(null, res.config) // 响应成功把已经完成的请求从 httpPending 中移除
})
5.通过 name 标识该请求是否能被撤销
export const httpRequest = (params) =>
    Http.post("/api", { name: "httpRequest", body: params })

实现原理

1.首先明确

axios 是对 XMLHttpRequest 的封装,撤销请求时,最终使用 XMLHttpRequest 的 abort()方法

2.axios 内部实现

CancelToken 模块暴露了一个 promise 执行撤销的时候会执行这个 promise 的 resolve

function CancelToken(executor) {
    if (typeof executor !== "function") {
        throw new TypeError("executor must be a function.")
    }

    var resolvePromise
    this.promise = new Promise(function promiseExecutor(resolve) {
        resolvePromise = resolve
    })

    var token = this
    executor(function cancel(message) {
        if (token.reason) {
            // Cancellation has already been requested
            return
        }

        token.reason = new Cancel(message)
        resolvePromise(token.reason)
    })
}

resolve 执行之后进入 then 里面的 function 执行最终的 abort()

config.cancelToken.promise.then(function onCanceled(cancel) {
    if (!request) {
        return
    }

    request.abort()
    reject(cancel)
    // Clean up request
    request = null
})