取消多次发送的请求,只保留最后一个

场景

        最近在开发询价功能的时候遇到一个问题,功能是传参调用接口获取价格,并在前端展示出来,每个参数的改变都会调用询价接口,重新获取价格。本来是没什么的,问题在于该接口的入参涉及多个参数,这部分代码的函数又彼此嵌套(吐槽...这部分代码阅读起来极其上头!),一个入参改变就会引起其他参数的改变,导致询价接口发生多次调用。

        理想情况下,前端只会展示最后一个接口返回的价格。然而实际情况是,接口的处理时间不同,导致最后一个响应的请求≠最后一个发送的请求。由于每次请求入参不同,导致返回结果,极端情况下,在调用了两三次请求后,搞不好最先发送的请求在最后返回,导致前端价格展示错误。

        价格展示极大关联用户感知,修改BUG迫在眉睫,第一次遇到这种重复请求问题的我开始寻找解决办法,最终经过亲测,有以下两种办法对我当前项目适用。

方法1. 使用全局Id标记该请求

        存储一个全局id,每次发送请求时+1,在接收请求回调时使用存储的id判断该回调是否为最后一次请求的回调。

// 页面初始化时,存储全局Id
onMounted(() => {
  sessionStorage.setItem('priceId', '0')
}
// 发送请求,只取最后一次请求的返回值
function queryPrice(data) {
  transmitData.priceLoading = true
  // 全局priceId加1
  const priceId = Number(sessionStorage.getItem('priceId')) + 1
  sessionStorage.setItem('priceId', String(priceId))
  // 调用接口发送请求
  queryCheckoutPrice(data)
    .then(res => {
      // 判断当前请求是否是最后一次请求,若是则取返回值
      if (String(priceId) === sessionStorage.getItem('priceId')) {
        //  处理数据并展示
        //  .............
      }
    })
    .catch(err => {
      console.log('err in queryCheckoutPrice', err)
    })
    .finally(() => {
      transmitData.priceLoading = false
    })
}

方法2. 使用Axios取消请求 - CancelToken

        Axios提供了取消请求的多种方法:取消请求 | Axios中文文档 | Axios中文网 (axios-http.cn)

1. AbortController:Axios 支持以 fetch API 方式—— AbortController 取消请求。

2. 使用CancelToken取消请求:

(1)调用 CancelToken 的静态方法 source 创建一个 cancel token。

(2)CancelToken 的构造函数实例化一个来创建 cancel token。

        

        这里我使用CancelToken的第2种方法,下面贴出主要代码:

import axios from 'axios'
import type { AxiosResponse, InternalAxiosRequestConfig } from 'axios'
import { defaultConfig } from './config'
import { isPlainObject, merge } from 'lodash-es'
import { ResponseStatus } from './httpCode'

const CancelToken = axios.CancelToken // axios取消token

const cancelTokenObj = {} // 用于储存每个请求的取消函数和请求标识

const whiteList = ['/queryCheckoutPriceForOp'] // 不允许多次重复调用的API名单

// 取消被多次发起的请求,只保留最后一个请求
const cancelAjax = (type, config) => {
  // 取出请求路径作为key
  const key = config.url
  if (type === 'check') {
    // 1. 若该url的请求未被记录,则记录改请求,并为其赋值取消函数
    if (!cancelTokenObj[key]) {
      config.cancelToken = new CancelToken(cancelFunc => {
        cancelTokenObj[key] = cancelFunc
      })
      // 2. 若该url的请求已存在,则取消之前对该url发起的请求. (取消上一个,保留本次请求)
    } else {
      if (cancelTokenObj[key] !== undefined) {
        cancelTokenObj[key]()
      }
    }
  } else if (type === 'remove') {
    // 最后一个请求结束,移除对该url请求的记录
    if (cancelTokenObj[key] !== undefined) {
      delete cancelTokenObj[key]
    }
  }
}


export function Http(options = {}) {
  // 创建axios实例
  const instance = axios.create(defaultConfig)

  // 请求拦截器,
  instance.interceptors.request.use(
    function (config: InternalAxiosRequestConfig) {
      // 添加请求取消功能
      if (whiteList.includes(config.url)) {
        cancelAjax('check', config)
      }
      return config
    },
    function (error) {
      return Promise.reject(error)
    }
  )

  // 响应拦截器
  instance.interceptors.response.use(
    // 默认的拦截器
    function (response: AxiosResponse<any, any>): AxiosResponse<any, any> | Promise<AxiosResponse<any, any>> {
      // 包含了code>=200和code<400的请求,同时满足业务code要求
      if (response?.data && Object.prototype.hasOwnProperty.call(response.data, 'returnCode')) {
        // 但是业务系统自己定义的业务code:returnCode=000000表示成功
        if (response.data.returnCode === ResponseStatus.SUCCESS) {
          // 最后一次请求获取完成,移除该请求取消功能
          cancelAjax('remove', response.config)
          return response.data
        } else {
          // 业务失败请求逻辑处理
          // ......
        }
      } else {
        // 其他失败请求逻辑处理
        // ......
        // throw new Error(`${response.config.url}接口返回数据格式错误`)
      }
    }
  )
  return instance
}

        需要注意的是,如果在请求被取消之前,请求已经被发送并成功响应,那么该已经成功响应的请求结果无法被取消。因此也需要对已经成功响应的请求结果做好处理。另外,也需要处理请求被取消的异常情况,以避免出现错误。

        目前暂时使用方法1.

TODO:

       CancelToken原理:axios的cancelToken取消机制原理 - 前端小记 - SegmentFault 思否  

参考:

【实战】Axios取消请求 - CancelToken - 简书

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值