axios 简单配置和取消重复请求

本文详细介绍了如何在axios中配置请求和响应拦截,实现取消重复请求的功能,包括设置token、处理不同HTTP方法的参数格式,以及使用debounce优化超时请求处理。

1、简单的 axios 请求和响应拦截配置

2、取消重复请求

import axios from 'axios'
import debounce from 'lodash/debounce'
import {
  Message
} from 'element-ui'

class Ajax {
  constructor() {
    // 初始化值
    this.init()
    // 初始化token
    this.getToken()
    // 初始化axios
    this.initAxios()
    // 打开请求拦截
    this.startReqInterceptors()
    // 打开响应拦截
    this.startRespInsterceptors()
    // 返回 axios 实例对象
    return this.ajax
  }

  /**
   * @method 初始化值
   */
  init() {
    this.overTimeValue = 30
    this.baseURL = '/new-trade-bank'
    this.pending = []
    this.CancelToken = axios.CancelToken
  }
  /**
  * 获取token
  */
  getToken() {
    this.token = localStorage.getItem('token')
    this._token = localStorage.getItem('ac_token')
  }

  /**
   * @method 初始化axios实例
   */
  initAxios() {
    this.ajax = axios.create({
      timeout: this.overTimeValue * 1000
    })
  }

  /**
   * @method 通过config获取到参数
   * @returns 对应参数的字符串
   */
  getParamFormConfig(config) {
    const paramsMethodMap = {
      get: 'params',
      // delete 请求 data和params都可以
      delete: 'data',
      post: 'data',
      put: 'data',
      patch: 'data'
    }
    // 参数
    const param = config[paramsMethodMap[config.method]]

    // 参数进行格式化
    const format = {
      '[object Undefined]': undefined,
      '[object Array]': JSON.stringify(param),
      '[object Object]': JSON.stringify(param),
      '[object Number]': param,
      '[object String]': param
    }
    // 返回字符串格式
    return format[Object.prototype.toString.call(param)]
  }

  /**
   * @method 打开请求拦截
   */
  startReqInterceptors() {
    const {
      token,
      _token
    } = this
    this.ajax.interceptors.request.use(config => {
      if (config.url) {
        // 包含/special开头的api做特殊处理,不用baseURL
        config.url = /^(\/special)/.test(config.url) ? config.url : this.baseURL + config.url
        // 发送ajax前,执行取消操作,便利数组 看是否重复发送
        this.removePending(config)
        // 设置token和请求头authorization
        if (token) {
          config.headers.token = token
        } else if (_token) {
          config.headers.Authorization = `bearer ${_token}`
        }
        // 将新请求添加进队列
        this.addPending(config)
      }

      // 做全局加载中
      return config
    })
  }

  /**
   * @method 打开响应拦截
   */
  startRespInsterceptors() {
    this.ajax.interceptors.response.use(resp => {
      // 当前请求已经响应结束,从pending队列中移除
      this.removePending(resp.config)
      let { data } = resp

      if (resp.status === 200) {
        const {
          filename
        } = resp.headers

        if (!filename && data.type && data.type !== 'success') {
          this.alertMsg(data)
        }
        if (data && filename) {
          // 对文件下载特殊处理
          const str = data
          data = {
            fileStream: str,
            filename
          }
        }

        return data || resp
      }
    }, err => {
      if (!err.response) {
        /* 重复发送请求,请求被取消了 */
        const {
          message
        } = err
        // 超时
        if (/^(timeout of)/.test(message)) {
          // 移除超时请求
          this.removeOverTimeRequest()
          this.alertMsg({
            type: 'error',
            text: '接口请求超时'
          })
        } else {
          console.warn('Warning: 重复发送了请求:' + err.message)
        }
      } else if (err.response.status === 401) {
        // 移除token
        const {
          token
        } = this

        window.localStorage.clear()
        // 没有权限跳转到权限认证页面
        window.location.href = token ? `/new-trade-bank/authorize?token=${token}` : '/new-trade-bank/authorize'
      } else if (err.response.status === 403) {
        this.alertMsg(err.response.data)
      } else if (err.response.status === 404) {
        console.warn('%c404-后台接口未找到', 'font-weight: bold;color: red;font-size: 16px;')
      } else if (err.response.status === 500) {
        this.alertMsg({
          type: 'error',
          text: '500-连接服务器出错'
        })
      }

      /**
       * 在这里对状态吗不是200的进行统一错误捕获
       * 注意: 这里代码执行完了 还是会走Promise.resolve
       *      也就是还是会走请求的.then函数
       * 返回可以按照自己需要进行返回,可以减少请求出错时的控制台报错
       */
      return Promise.reject(err)
    })
  }

  /**
   * @method 请求成功,移除队列中的请求
   */
  removePending(target) {
    // target的param需要经过格式化成相同字符串才能比较
    const targetParam = this.getParamFormConfig(target)
    for (const [index, p] of Object.entries(this.pending)) {
      // url,method,param需要完全一致才能确定是重复请求
      if (p.url === target.url && p.method === target.method && targetParam === p.param) {
        // 当当前请求在数组中存在执行函数体,取消队列中的请求并删除,执行cancel函数会传递一个
        // cancel对象给promise.reject, cancel里面的参数会传递到response中的err对象的message中
        p.cancel(`${p.method} ${p.url}`)
        // 从队列中删除
        this.pending.splice(index, 1)
      }
    }
  }
  /**
   * @method 将请求添加进队列中
   */
  addPending(config) {
    config.cancelToken = new axios.CancelToken(c => {
      this.pending.push({
        url: config.url,
        method: config.method,
        cancel: c,
        param: this.getParamFormConfig(config),
        createTime: Date.now()
      })
    })
  }

  /**
   * @method 通过element的message组件进行统一弹窗告警
   */
  alertMsg(data) {
    // 关闭现在有的告警信息
    Message.closeAll()
    Message({
      showClose: true,
      message: data.text,
      type: data.type
    })
  }
}

/**
 * @method 移除pending队列中的超时请求
 * @description 通过防抖避免频繁调取
 */
Ajax.prototype.removeOverTimeRequest = debounce(function () {
  const nowDate = Date.now()

  for (const p in this.pending) {
    const {
      createTime
    } = this.pending[p]
    const time = nowDate - createTime
    if (time >= (this.overTimeValue * 1000)) {
      this.pending.splice(p, 1)
    }
  }
}, 100)

const ajax = new Ajax()

export default ajax

参考链接:

 axios的cancelToken

Axios取消请求 - CancelToken

axios中文文档|axios中文网

Axios 使用小结: API, response对象, 错误处理

### 使用 Axios 封装取消重复请求 为了实现取消重复请求的功能,可以利用 `axios` 提供的 `CancelToken` 拦截器机制。下面是一个完整的解决方案,展示了如何通过自定义方法来管理待处理的请求并确保不会发出重复请求。 #### 创建全局变量存储待处理请求 首先声明一个 Map 对象用于保存当前所有的挂起请求及其对应的取消函数: ```javascript const pendingRequests = new Map(); ``` #### 定义辅助工具类 接着编写几个帮助函数以便于后续逻辑调用: - **getPendingRequestKey**: 构建唯一键名作为标识符; - **addPendingRequest**: 向集合中添加新成员; - **removePendingRequest**: 移除已完成或被终止的任务; ```javascript // 获取唯一的请求key function getPendingRequestKey(config) { return [ config.method, config.url, JSON.stringify(config.params), JSON.stringify(config.data) ].join('&'); } // 添加到pending队列里 function addPendingRequest(config, cancelFn) { const key = getPendingRequestKey(config); if (!pendingRequests.has(key)) pendingRequests.set(key, cancelFn); } // 从pending队列删除 function removePendingRequest(config) { const key = getPendingRequestKey(config); if (pendingRequests.has(key)) { const cancelFn = pendingRequests.get(key); cancelFn('Duplicate request was cancelled.'); pendingRequests.delete(key); } } ``` #### 配置 axios 请求/响应拦截器 接下来设置两个拦截器分别负责在每次发起 HTTP 调用前检查是否有相同 URL 正处于等待状态以及接收服务器返回的数据后清理掉对应条目: ```javascript import axios from 'axios'; // 设置默认超时时间基础路径等参数... axios.defaults.timeout = 5000; axios.defaults.baseURL = '/api/'; // 请求拦截器 axios.interceptors.request.use( config => { // 判断是否允许取消 if (config.allowCancel !== false) { // 检查是否存在相同的请求 removePendingRequest(config); // 新增cancel token config.cancelToken = new axios.CancelToken(cancel => { addPendingRequest(config, cancel); }); } return config; }, error => Promise.reject(error) ); // 响应拦截器 axios.interceptors.response.use( response => { removePendingRequest(response.config); return response; }, error => { if (axios.isCancel(error)) { console.warn(`Request canceled: ${error.message}`); } else { // 清理失败的请求 removePendingRequest(error.config || {}); } return Promise.reject(error); } ); ``` #### 发送带有取消功能的 GET 请求实例 最后展示一段实际应用场景下的代码片段,这里假设有一个获取加密货币行情数据的需求场景: ```javascript async function fetchCryptoData(params) { try { const result = await axios({ method: 'GET', url: '/crypto/prices', // API endpoint params, allowCancel: true // 开启此选项以启用去重特性 }); console.log(result.data); // 输出结果 return result; } catch (e) { throw e; } } ``` 上述代码实现了基于 `axios` 库的一套简单而有效的防止重复提交方案[^2]。每当有新的请求到来时,程序会先尝试查找是否存在完全一致的历史记录——即同一 URI 下携带相等查询字符串与 body 参数组合而成的关键字存在于 `pendingRequests` 中;一旦发现匹配项则立即执行关联的取消指令,并更新映射表使其保持最新状态。反之亦然,在正常情况下新增加一条记录直至收到服务端反馈为止才会将其清除出去。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值