基于Axios实现请求防重

59 阅读1分钟

一、Axios简介

Axios基于promise的网络请求库,作用于node.js和浏览器。

在服务端它使用原生 node.js http 模块, 而在客户端 (浏览端) 则使用 XMLHttpRequests。

特性:

  • 从浏览器创建 XMLHttpRequests
  • 从 node.js 创建 http 请求
  • 支持 Promise API
  • 拦截请求和响应
  • 转换请求和响应数据
  • 取消请求,本文使用到的api
  • 自动转换JSON数据
  • 客户端支持防御XSRF

二、实现

import axios from 'axios'
/*客户端发起的请求合集*/
const ajaxExecutingMap = new Map()
/**
 * @desc 构建请求合集所用key
 * @param url 当前请求的url
 * @param data 当前请求的所有入参
 * @param requestMethod 请求方法以及数据传输方式
 */
const buildAjaxKey = (url, data, requestMethod) => {
  const ajaxKey = `${requestMethod}: ${url}-${JSON.stringify(data)}`
  return {
    ajaxKey
  }
}

/**
 * @desc 清除请求合集中已返回的请求
 * @param config 请求的config属性
 */
const clearFinishAjax = (config) => {
  let { url, data, params, method } = config
  const reqData = data ? JSON.parse(data) : {}
  const reqParams = params ? JSON.parse(params) : {}
  const configData = {
    ...reqData,
    ...reqParams
  }
  const { ajaxKey } = buildAjaxKey(url, configData, method)
  if (ajaxExecutingMap.get(ajaxKey)) {    
    ajaxExecutingMap.delete(ajaxKey)
  }
}

/**
 * @desc 处理重复请求
 * @param config 请求的config属性
 */
const dealReplayModel = (config) => {
  // 调用axios的取消请求方法
  const CancelToken = axios.CancelToken
  const { data, params, method, url, antiReplayModel } = config
  const paramsData = {
    ...data,
    ...params
  }
  const { ajaxKey } = buildAjaxKey(url, paramsData, method)
  const oldAjaxCanceler = ajaxExecutingMap.get(ajaxKey)
  if (oldAjaxCanceler) {
    /* 丢弃新请求 */
    if (antiReplayModel) {
      return {
        executeAjax: false, /* 不执行当前请求 */
        config
      }
    } else {
      /* 取消旧请求 */
      oldAjaxCanceler.cancelSource.cancel(`旧的请求: ${ajaxKey} 被取消`)
      /* 将请求合集旧的请求数据进行删除 */
      ajaxExecutingMap.delete(ajaxKey)
    }
  }
  /* 将新的请求存储到请求合集 */
  const cancelSource = CancelToken.source()
  ajaxExecutingMap.set(ajaxKey, {
    cancelSource
  })
  config.cancelToken = cancelSource.token
  return {
    executeAjax: true, /* 执行当前请求 */
    config
  }
}

const httpService = axios.create()
httpService.interceptors.request.use((config) => {
  /* 重复请求拦截,如果配置antiReplayModel为true,则重复的情况下丢弃新请求,若为false,则重复的情况下取消旧请求,其他情况不做处理 */
  if (typeof config.antiReplayModel === 'boolean') {
    const antiReplayObj = dealRepeatModel(config)
    if (!antiReplayObj.executeAjax) {      
      return Promise.reject(new Error(`请求重复 ${config.url}`))
    }
    config = antiReplayObj.config
  }
})
httpService.interceptors.response.use((response) => {
  /* 若有重复请求拦截,则这里处理结束拦截 */
  if (typeof response.config.antiReplayModel === 'boolean') {
    clearFinishAjax(response.config)
  }
},(error) => {
  /* 在这里查看重复处理过程中抛出的提示 */
  console.log(error)
})

 /* 举个例子 */
httpService({
  url: 'api/get',
  method: 'post',
  data: {},
  antiReplayModel: true
})