Axios+TS使用AbortController封装重复请求拦截的类

901 阅读2分钟

取消重复请求依据

当多个请求的method(get,post,put等),urlparamsdata 完全相同时,我们可以认为他们是相同请求。而当后端还没有返回数据,此时又发起了相同的请求,就可以对该请求进行拦截。

封装思路

  • 根据当前请求生成KEY。可以使用qs 库或者手写一个queryString,来生成一个requestKey标注这些请求。(对应下面代码中的generateReqKey方法)

  • 如果该请求的requestKey在pendingRequestmap中不存在,则新创建一个controller对象(new AbortController),并将controller的signal属性挂载到请求的config属性中。否则就获取之前的controller,并挂载到当前请求的config属性上。(对应addPendingRequest方法)

    挂载singal的目的是为了之后能够取消请求。注意,相同的requestKey(相同的请求)要挂载相同的singal方便后面统一取消重复请求。

    const controller = new AbortController();
    // controller具有一个方法 `abort()`和一个属性 `signal`
    复制代码
    
  • 检查是否存在重复请求,若存在则取消已发请求。(对应removePendingRequest方法)

    controller.abort()
    

封装后的代码

    
    // 拦截重复请求
    import type { AxiosRequestConfig } from 'axios'
    // 从 v0.22.0 开始,Axios 支持以 fetch API 方式—— AbortController 取消请求:
    // CancelToken 已经被淘汰了

    export class RequestCanceler {
        // 存储每个请求的标志和取消函数
        pendingRequest: Map<string, AbortController>
        constructor() {
            this.pendingRequest = new Map<string, AbortController>()
        }
        generateReqKey(config: AxiosRequestConfig): string {
            const { method, url, params, data } = config
            return [url || '', method || '', queryString(params || {}), queryString(data || {})].join('&')
    }
        addPendingRequest(config: AxiosRequestConfig) {
            const requestKey: string = this.generateReqKey(config)
            if (!this.pendingRequest.has(requestKey)) {
                    const controller = new AbortController()
                    // 给config挂载signal
                    config.signal = controller.signal
                    this.pendingRequest.set(requestKey, controller)
            } else {
                    // 如果requestKey已经存在,则获取之前设置的controller,并挂载signal
                    config.signal = (this.pendingRequest.get(requestKey) as AbortController).signal
            }
        }
        removePendingRequest(config: AxiosRequestConfig) {
            const requestKey = this.generateReqKey(config)
            if (this.pendingRequest.has(requestKey)) {
                    // 取消请求
                    (this.pendingRequest.get(requestKey) as AbortController).abort()
                    // 从pendingRequest中删掉
                    this.pendingRequest.delete(requestKey)
            }
        }
    }

    // 序列化对象,推荐用qs库,这里就简单的手写吧
    interface Obj {
        [name: string]: number | null | string | undefined | boolean
    }
    function queryString(obj: Obj): string {
        const params = new URLSearchParams()
        // 可能是对象或者stringify后的对象
        if (typeof obj === 'string') {
                obj = JSON.parse(obj) as Obj
        }
        Object.keys(obj).forEach(key => {
                params.append(key, String(obj[key]))
        })
        return params.toString()
    }

使用

    import axios from 'axios'
    import { history } from 'umi'
    import { RequestCanceler } from './canceler.ts'

    const canceler = new RequestCanceler()
    const api = axios.create({

      // baseURL: '/api',
      baseURL: '/mock/api',
      withCredentials: true

      // timeout: 500
    })

    // 请求拦截
    api.interceptors.request.use((config) => {
      let token = localStorage.getItem('sid')
      if (token) {

        // 设置请求头
        config.headers[ 'Authorization' ] = 'Bearer ' + token
      }
      if (config.download) {

        // download设置responseType
        config.responseType = 'blob'
      }
       // 检查是否存在重复请求,若存在则取消已发的请求
       canceler.removePendingRequest(config)
       // 把当前的请求信息添加到pendingRequest
       canceler.addPendingRequest(config)
      return config
    },
    (error) => Promise.reject(error))

    // 返回拦截
    api.interceptors.response.use((response) => {
      if (response.status === 200) {
        if (response.config.download) {

          // download
          // 转换文件下载路径
          let objectUrl = URL.createObjectURL(new Blob([response.data]))

          // 获取文件名
          let fileName = decodeURI(response.headers[ 'content-disposition' ].split('=')[ 1 ])
          const link = document.createElement('a')
          link.download = fileName
          link.href = objectUrl
          link.click()
          canceler.removePendingRequest(response.config)
          return Promise.resolve(response)
        }
        canceler.removePendingRequest(response.config)
        return Promise.resolve(response.data)
      } else {
        canceler.removePendingRequest(response.config)
        return Promise.reject(response)
      }
    },
    (error) => {
      canceler.removePendingRequest(error.config || {})
      if (error.response.status === 401) {
        history.push('/?overtime=true')
      }
      return Promise.reject(error.response)
    })

    export default api

总结:亲测有效,放心食用

转载:juejin.cn/post/715429… ,感谢分享