Axios 封装:处理重复调用与内容覆盖问题

问题描述&背景

  • 下拉选择框,支持搜索,搜索时携带参数调用接口并更新下拉选项
  • 下拉选择连续进行多次搜索,先请求但响应时间长的返回值会覆盖后请求但响应时间短的
  • 举例:
    • 搜索后先清空选项,再输入内容进行搜索。清空后查询全量数据接口响应时间更长,覆盖搜索过滤后的数据

问题分析

  • 连续多次请求导致问题
    • 通过防抖debounce函数,限制短期内无法重复调用接口 - 使用 lodashdebounce 函数实现
    • 若接口响应时间相差较大,仍会有覆盖问题,需要结合以下方法
  • 接口响应慢导致问题
    • 优化接口,如减少后端非必要属性的计算,提高响应速度 - 后端优化
  • 接口调用重复问题
    • 通过一些方法保证最后显示的数据为最晚发起的那个请求返回的值,方案见下文

方案选择及具体实施

  1. 在前一个请求响应前,阻止发起下一个请求(问题中通过禁用选择框 disabled=true 实现),避免返回值覆盖
    • 实现方法
      • 接口请求时将组件的disabledloading均设置为true
      • 返回后设置为false
    • 优点
      • 可以减少接口调用,防止返回值相互覆盖
    • 缺点
      • 禁用选择框会让其失去焦点,用户需要再次点击输入
      • 禁用状态的切换使得操作不连贯,用户有明显的感知,体验下降
      • 需要操作页面元素,需要额外代码
  2. 发送请求时,通过给每次请求添加一个序列号或时间戳,然后在处理响应时进行匹配,确保每次返回的结果与其对应
    • 实现方法
      • 发送请求时生成唯一的标识符(时间戳)
      • 处理响应时保存该标识符
      • 匹配标识符更新数据
    • 优点
      • 可以找出最新的请求赋值,保证数据为最后请求的
    • 缺点
      • 需要多次更新使用的数据
      • 需要生成标识并用额外的变量储存标识,逻辑改动较大
      • 没有实际减少或取消无效的请求,占用资源多
  3. 发起新的请求时取消尚未完成的请求 ⭐️
    • 运用技术
      • axios的取消请求:axios取消请求
      • 项目使用的axios版本为0.21.1,使用CancelToken
      • 更高版本使用AbortControllerAbortController:AbortController() 构造函数

AbortController实现方法

const controller = new AbortController();
const {
  data: { data },
} = await this.$http.get('/api/v1/xxx'
  params,
  signal: controller.signal
})
// 取消请求
controller.abort()

CancelToken实现方法

  • 在data中定义cancelToken用于保存当前请求token
data() {
  return {
    ...
    cancelToken: null,
  }
},
  • 在查询方法中进行如下配置
// 防抖
searchOptions: debounce(
  async function (searchString) {
    // 取消上一次的请求
    if (this.cancelToken) {
      this.cancelToken.cancel()
    }
    // 创建 cancelToken
    this.cancelToken = axios.CancelToken.source()
    this.loading = true
    const params = {...}
    const {
      data: { data },
    } = await this.$http.get('/api/v1/xxx'
      params,
      cancelToken: this.cancelToken.token, // 请求时传入token
    })
    // 数据处理...
    this.loading = false
    // 清除cancelToken
    this.cancelToken = null
  },
  300,
  {
    leading: false,
    trailing: true,
  }
),
  1. 使用第三方插件进行优化 ⭐️
    • 插件名称:axios-extensions
    • 功能:缓存请求结果,节流请求,请求重试
    • 缓存请求:cacheAdapterEnhancer
      cacheAdapterEnhancer(axios.defaults.adapter, option)
      • option 对象,可选
        • enabledByDefault:是否默认缓存,Boolean类型, 默认是true(缓存), false(不缓存)
        • cacheFlag:是否通过flag方式缓存,字符串类型, 只有flag一样才会缓存,flag不对或者没设置的都不会缓存。
        • defaultCache:可以配置maxAge(缓存有效时间, 毫秒单位),默认是5分钟,max(支持缓存的请求的最大个数),默认是100个
    • 完整代码 🚀
import axios from 'axios'
import { Cache, cacheAdapterEnhancer } from 'axios-extensions'

const request = axios.create({
  baseURL: process.env.BASE_URL,
  adapter: cacheAdapterEnhancer(axios.defaults.adapter, {
    defaultCache: new Cache({ maxAge: 2000, max: 100 }),
  }),
})
  • 扩展:
    • 节流请求:throttleAdapterEnhancer
      throttleAdapterEnhancer(adapter, options)
      • option 对象,可选
        • threshold:限制请求调用的毫秒数,数字类型, 默认是1000
        • cache:可以配置 max(节流请求的最大个数),默认是100个
    • 请求重试:retryAdapterEnhancer
      retryAdapterEnhancer(adapter, options)
      • option 对象,可选
        • times:重试的次数,Number类型, 默认是2,请求失败后会重试2次。

优化效果

  • 杜绝先发起的请求结果覆盖旧发起的请求结果的情况
  • 新的请求发起,取消当前进行中的请求,减少无用的请求调用
  • 无需修改其他的业务逻辑,无需引入更多变量记录请求状态
  • 用户没有感知,不会增加额外的用户操作

功能封装

  • 封装一个基于 axios 的 HTTP 请求管理类:http.ts 🚀
import axios, { AxiosRequestConfig, CancelTokenSource } from 'axios'

// 引入请求函数 包含一些请求拦截器或其他设置
import { request } from './axiosConfig' 

// 枚举 指定响应数据的格式(这里只举例1种返回体格式)
enum ResponseType {
  ResData, // 返回 res.data
}
// 完整的响应对象结构
interface ApiResponse<T> {
  data: {
    code: number
    message: string
    data: T | null | undefined
  }
  status: number
  headers?: Record<string, string>
  config?: any
  request?: any
}
// 异步请求的结果
type HttpResult<T> = Promise<T | ApiResponse<T> | any> 

// 扩展了 Axios 的请求配置,添加了两个自定义字段以支持请求取消功能
interface CustomAxiosRequestConfig extends AxiosRequestConfig {
  cancelPrevious?: boolean // 是否取消之前的请求
  cancelTokenId?: string // 保存取消请求tokenId
}

class Http {
  // 存储 Http 类的实例,以便实现单例模式
  private static instancesMap: Map<string, Http> = new Map()
  // 存储与请求 URL 关联的取消令牌源,用于实现请求取消功能
  private static cancelTokenIdSourceMap: Map<string, CancelTokenSource> =
    new Map()
  private requestFunction: (config: AxiosRequestConfig) => Promise<any> // 请求方法
  private responseType: ResponseType = ResponseType.ResData // 相应数据格式类型

  // 构造函数-接收参数以配置请求函数、响应类型和公共URL前缀,同时初始化相关属性
  constructor({
    requestMethod = request,
    responseType = ResponseType.ResData,
  }: {
    requestMethod?: (config: AxiosRequestConfig) => Promise<any>
  }) {
    this.requestFunction = requestMethod
    this.responseType = responseType
  }
  // 私有异步方法,用于执行 HTTP 请求,接受请求方法、URL 和配置
  private async createRequest<T>({
    method,
    url,
    config,
  }: {
    method: 'get' | 'post' | 'delete' | 'put'
    url: string
    config: CustomAxiosRequestConfig
  }): HttpResult<T> {
    let source, cancelTokenId
    if (config?.cancelPrevious) {
      // 取消之前的请求
      cancelTokenId = config?.cancelTokenId ?? this.getCancelTokenId(url)
      this.cancelPreviousRequest(cancelTokenId)
      // 创建新的取消令牌
      source = axios.CancelToken.source()
    }

    // 准备请求配置
    const requestConfig: AxiosRequestConfig = {
      ...config,
      method,
      url,
      cancelToken: source?.token,
    }

    // 请求
    try {
      // 保存取消令牌
      if (cancelTokenId) Http.cancelTokenIdSourceMap.set(cancelTokenId, source)
      // 发起请求
      const res = await this.requestFunction(requestConfig)
      // 没有遇到重复请求-清空取消令牌
      if (cancelTokenId) Http.cancelTokenIdSourceMap.delete(cancelTokenId)
      // 返回响应值
      if (this.responseType === ResponseType.ResData) {
        return res.data as T
      } else {
        return res as ApiResponse<T>
      }
    } catch (error) { // 错误处理
      if (axios.isCancel(error)) {
        console.error('Request canceled', error.message)
      } else {
        if (cancelTokenId) Http.cancelTokenIdSourceMap.delete(cancelTokenId)
        console.error('Error:', error)
      }
      throw error
    }
  }

  private cancelPreviousRequest(cancelTokenId: string): void {
    const source = Http.cancelTokenIdSourceMap.get(cancelTokenId)
    source?.cancel(`Cancelled request ${cancelTokenId}`)
  }

  private getCancelTokenId(url: string): string {
    return url.split('?')[0] // 提取非 query 部分, 防止同一个get请求不同query时没取消
  }

  // 实现get方法
  public get<T>(
    url: string,
    config?: CustomAxiosRequestConfig
  ): HttpResult<T> {
    return this.createRequest<T>({ method: 'get', url, config })
  }

  // 实现post方法
  public post<T>(
    url: string,
    data?: any,
    config?: CustomAxiosRequestConfig
  ): HttpResult<T> {
    return this.createRequest<T>({
      method: 'post',
      url,
      config: { ...config, data },
    })
  }

  // 实现delete方法
  public delete<T>(
    url: string,
    config?: CustomAxiosRequestConfig
  ): HttpResult<T> {
    return this.createRequest<T>({ method: 'delete', url, config })
  }

  // 实现put方法
  public put<T>(
    url: string,
    data?: any,
    config?: CustomAxiosRequestConfig
  ): HttpResult<T> {
    return this.createRequest<T>({
      method: 'put',
      url,
      config: { ...config, data },
    })
  }

  // 单例
  // 该方法检查是否已经存在相同 ID 的实例,如果不存在,则创建一个新的实例并存储在 instancesMap 中。
  // 这样做的目的是减少同类实例的创建,确保在应用中使用的是同一个 Http 实例,从而管理配置和状态
  public static getInstance({
    requestMethod = request,
    responseType = ResponseType.ResData,
    instanceId = 'http',
  }: {
    requestMethod?: (config: AxiosRequestConfig) => Promise<any>
    responseType?: ResponseType
    instanceId?: string
  }): Http {
    let instance = Http.instancesMap.get(instanceId)
    if (!instance) {
      instance = new Http({ requestMethod, responseType })
      Http.instancesMap.set(instanceId, instance)
    }
    return instance
  }
}

// 导出实例
export const http = Http.getInstance({
  requestMethod: request,
  responseType: ResponseType.ResData,
  instanceId: 'http',
})

  • 补充:
// Axios 请求实例
const request = axios.create({
  baseURL: process.env.BASE_URL,
  adapter: cacheAdapterEnhancer(axios.defaults.adapter, {
    defaultCache: new Cache({ maxAge: 2000, max: 100 }),
  }),
})
  • 使用
await this.$http.post(
  `/xxx/xxx/${this.id}/xxx`,
  params
)

参考文档

来学习下axios的扩展插件1
来学习下axios的扩展插件2

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/955082.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Unity2017 控制.abc格式的三维动画播放

首先需要导入插件Alembic&#xff0c;否则导入abc动画&#xff0c;Unity是不会识别的。 Unity2017版本及以下直接从我这儿下载&#xff1a;https://download.csdn.net/download/qq_41603955/90272382 高版本Unity&#xff0c;请移步AssetStore商店搜找。 导入abc之后&#x…

python管理工具:conda部署+使用

python管理工具&#xff1a;conda部署使用 一、安装部署 1、 下载 - 官网下载&#xff1a; https://repo.anaconda.com/archive/index.html - wget方式&#xff1a; wget -c https://repo.anaconda.com/archive/Anaconda3-2023.03-1-Linux-x86_64.sh2、 安装 在conda文件的…

挖掘机检测数据集,准确识别率91.0%,4327张原始图片,支持YOLO,COCO JSON,PASICAL VOC XML等多种格式标注

挖掘机检测数据集&#xff0c;准确识别率91.0%&#xff0c;4327张图片&#xff0c;支持YOLO&#xff0c;COCO JSON&#xff0c;PASICAL VOC XML等多种格式标注 数据集详情 数据集分割 训练组70&#xff05; 3022图片 有效集20&#xff05; 870图片 测试集10&…

vue 学习笔记 - 创建第一个项目 idea

1、安装Vue CLI 查看npm版本号 &#xff08;可跳过&#xff09; % npm -v 11.0.0安装Vue CLI % npm install -g vue/cli2、创建项目 进入工程文件目录 % cd /Users/ruizhifeng/work/aina-client查看vue 版本号 &#xff08;可跳过&#xff09; % vue --version vue/cli 5…

BIO、NIO、AIO

第一章 BIO、NIO、AIO课程介绍 1.1 课程说明 ​ 在Java的软件设计开发中,通信架构是不可避免的,我们在进行不同系统或者不同进程之间的数据交互,或者在高并发下的通信场景下都需要用到网络通信相关的技术,对于一些经验丰富的程序员来说,Java早期的网络通信架构存在一些缺…

SpringMVC复习笔记

文章目录 SpringMVC 概念和基本使用SpringMVC 简介SpringMVC 核心组件和调用流程SpringMVC 基本使用第一步&#xff1a;导入依赖第二步&#xff1a;Controller 层开发第三步&#xff1a;SpringMVC 配置类配置核心组件第四步&#xff1a;SpringMVC 环境搭建第五步&#xff1a;部…

NEC纪实 :2024全国机器人大赛 Robocon 常州工学院团队首战国三

全国机器人大赛 Robocon 常州工学院团队首战国三 通宵7天7夜&#xff0c;常州工学院RC团队&#xff0c;首次闯入全国机器人大赛国赛&#xff0c;并成功得分&#xff01; 不同于老牌强队&#xff0c;常州工学院&#xff08;下面用"常工"代替&#xff09;的这只队伍&…

Golang结合MySQL和DuckDB提高查询性能

要在Golang中组合MySQL和DuckDB以提高查询性能&#xff0c;请考虑使用混合查询执行方法。这种方法利用了MySQL强大的事务管理和DuckDB闪电般的分析处理能力。本文介绍如何充分利用两者的方法。 各取所长 用MySQL处理事务&#xff0c;用DuckDB处理分析 MySQL应该处理常规的INS…

数据结构-单向不带头不循环链表

链表知识总结 逻辑结构&#xff1a;线性结构&#xff08;元素之间存在一对一关系&#xff09; 存储结构&#xff08;物理结构&#xff09;&#xff1a;链式存储&#xff08;存储顺序和逻辑顺序不在乎是否一致&#xff09; 1.链表的特点&#xff1a;擅长进行动态删除和增加操作&…

28:CAN总线入门一:CAN的基本介绍

CAN总线入门 1、CAN总线简介和硬件电路1.1、CAN简要介绍1.2、硬件电路1.3、CAN总线的电平标准 2、帧格式2.1、数据帧&#xff08;掌握&#xff09;2.2、遥控帧&#xff08;掌握&#xff09;2.3、错误帧&#xff08;了解&#xff09;2.4、过载帧&#xff08;了解&#xff09;2.5…

2018年西部数学奥林匹克几何试题

2018G1 在 △ A B C \triangle ABC △ABC 中, O O O 为外心, M M M 为边 B C BC BC 的中点, 延长 A B AB AB 交 ( A O M ) (AOM) (AOM) 于点 D D D, ( A O M ) (AOM) (AOM) 交 A C AC AC 于点 E E E. 求证: E C D M ECDM ECDM. 证明: 设点 G G G 为 △ A B C …

知识图谱抽取分析中,如何做好实体对齐?

在知识图谱抽取分析中&#xff0c;实体对齐是将不同知识图谱中的相同实体映射到同一表示空间的关键步骤。为了做好实体对齐&#xff0c;可以参考以下方法和策略&#xff1a; 基于表示学习的方法&#xff1a; 使用知识图谱嵌入技术&#xff0c;如TransE、GCN等&#xff0c;将实体…

UnityXR Interaction Toolkit 如何检测HandGestures

前言 随着VR设备的不断发展,从最初的手柄操作,逐渐演变出了手部交互,即头显可以直接识别玩家的手部动作,来完成手柄的交互功能。我们今天就来介绍下如何使用Unity的XR Interaction Toolkit 来检测手势Hand Gesture。 环境配置 1.使用Unity 2021或者更高版本,创建一个项…

Maven在Win10上的安装教程

诸神缄默不语-个人CSDN博文目录 这个文件可以跟我要&#xff0c;也可以从官网下载&#xff1a; 第一步&#xff1a;解压文件 第二步&#xff1a;设置环境变量 在系统变量处点击新建&#xff0c;输入变量名MAVEN_HOME&#xff0c;变量值为解压路径&#xff1a; 在系统变…

高等数学学习笔记 ☞ 不定积分与积分公式

1. 不定积分的定义 1. 原函数与导函数的定义&#xff1a; 若函数可导&#xff0c;且&#xff0c;则称函数是函数的一个原函数&#xff0c;函数是函数的导函数。 备注&#xff1a; ①&#xff1a;若函数是连续的&#xff0c;则函数一定存在原函数&#xff0c;反之不对。 ②&…

KHOJ的安装部署

KHOJ的部署记录 KHOJ是一个开源的AI对话平台&#xff08;github标星超2w&#xff09;&#xff0c;有免费版本&#xff08;https://app.khoj.dev/&#xff09;。但本地部署&#xff0c;可以保证自己的文件安全&#xff0c;另外一方面&#xff0c;有数据库能随时查询过去自己的所…

windows 搭建flutter环境,开发windows程序

环境安装配置&#xff1a; 下载flutter sdk https://docs.flutter.dev/get-started/install/windows 下载到本地后&#xff0c;随便找个地方解压&#xff0c;然后配置下系统环境变量 编译windows程序本地需要安装vs2019或更新的开发环境 主要就这2步安装后就可以了&#xff0…

Jupyter notebook中运行dos指令运行方法

Jupyter notebook中运行dos指令运行方法 目录 Jupyter notebook中运行dos指令运行方法一、DOS(磁盘操作系统&#xff09;指令介绍1.1 DOS介绍1.2 DOS指令1.2.1 DIR - 显示当前目录下的文件和子目录列表。1.2.2 CD 或 CHDIR - 改变当前目录1.2.3 使用 CD .. 可以返回上一级目录1…

SpringMVC——原理简介

狂神SSM笔记 DispatcherServlet——SpringMVC 的核心 SpringMVC 围绕DispatcherServlet设计。 DispatcherServlet的作用是将请求分发到不同的处理器&#xff08;即不同的Servlet&#xff09;。根据请求的url&#xff0c;分配到对应的Servlet接口。 当发起请求时被前置的控制…

Python从0到100(八十三):神经网络-使用残差网络RESNET识别手写数字

前言: 零基础学Python:Python从0到100最新最全教程。 想做这件事情很久了,这次我更新了自己所写过的所有博客,汇集成了Python从0到100,共一百节课,帮助大家一个月时间里从零基础到学习Python基础语法、Python爬虫、Web开发、 计算机视觉、机器学习、神经网络以及人工智能…