前端重复请求是一个很常见的问题,现有的解决方案有很多,如:

  • 申明一个状态变量
  • debounce 和 throttle
  • axios 的 CancelToken

申明变量方式

// 伪代码
let isShow = false // 初始状态

async handleFunc () {
  isShow = true 	 // isShow 为 true 时,按钮置灰等
  await this.$apis.xxx()
  isShow = false
}

// 这种方式的劣势是和业务代码耦合,每个请求都需要设置状态变量控制

debounce 和 throttle

防抖和节流函数也是针对于频繁的事件触发,如 resize、input、scroll 等,具体操作可以看附录大佬的文章,今天主要看如何利用 axios 来全局阻止重复请求

axios 的 CancelToken

在我们的项目中,一般都会对 axios 进行二次封装去使用,对请求、响应拦截器进行全局操作,如 HTTP 状态码、业务标识处理、全局 loading 以及我们今天说的阻止重复请求

axios 中是利用 CancelToken 来中止请求的,其实这就是原生 xhr.abort() ,具体逻辑如下:

import axios, { AxiosResponse, AxiosRequestConfig, AxiosError } from 'axios'
import router from '@/router'
import Vue from 'vue'

const flagMsg: string = 'FASTCLICK' // 连续点击错误标识
let pending: any[] = []
const cancelToken = axios.CancelToken // 初始化取消请求的构造函数

const removePending = (config: any, fn?: any) => {
  const arr = config.url.split('/api') // 处理 baseURL
  const flagUrl = arr[arr.length - 1]
  if (pending.includes(flagUrl)) {
    fn ? fn(flagMsg) : pending.splice(pending.indexOf(flagUrl), 1)
  } else {
    if (fn) {
      pending.push(flagUrl)
    }
  }
}

// 创建 axios 实例
const instance = axios.create({
  baseURL: '/api',  // api 的 base_url
  timeout: 60000,   // 请求超时时间
})

/**
 * request 拦截器
 * @param config request拦截器
 */
const requestInterceptor = (config: AxiosRequestConfig) => {
  config.cancelToken = new cancelToken(c => {
    removePending(config, c)
  })
  return config
}

/**
 * request 拦截器
 * @param response 返回拦截器
 */
const responseInterceptor = (response: AxiosResponse<any>) => {
  removePending(response.config)
  const code = response.data.code
  switch (code) {
    case 'LOGIN_FAILED':
    // 省略业务状态码
    case 'TOKEN_EXPIRED': {
      logOut() // 退出登录
      setTimeout(() => {
        router.push({ name: 'SignIn' })
      }, 800)
    }
    default:
      break
  }
  return response
}

/**
 * 请求错误处理
 * @param error error 实例
 */
const errorRequest = (error: AxiosError) => {
  Vue.$handleError(error)
}

/**
 * 响应错误处理
 * @param error error 实例
 */
const errorResponse = (error: AxiosError) => {
  pending = []
  if (error.message === flagMsg) {
    throw new Error('Jangan sering klik')
  }
  Vue.$handleError(error)
}

instance.interceptors.request.use(requestInterceptor, errorRequest)
instance.interceptors.response.use(responseInterceptor, errorResponse)

以上我们可以阻止点击造成的频繁请求,后续还可以加上全局 loading 或者 token 等,这个部分就不在这里写了,可以自己尝试一下

参考附录