基于Axios封装请求---防止接口重复请求解决方案
一、引言
前端接口防止重复请求的实现方案主要基于以下几个原因:
-
用户体验:重复发送请求可能导致页面长时间无响应或加载缓慢,从而影响用户的体验。特别是在网络不稳定或请求处理时间较长的情况下,这个问题尤为突出。
-
服务器压力:如果前端不限制重复请求,服务器可能会接收到大量的重复请求,这不仅增加了服务器的处理负担,还可能导致资源浪费。
-
数据一致性:对于某些操作,如表单提交,重复请求可能导致数据重复插入或更新,从而破坏数据的一致性。
为了实现前端接口防止重复请求,可以采取以下方案:
-
设置请求标志:在发送请求时,为请求设置一个唯一的标识符(如请求ID)。在请求处理过程中,可以通过检查该标识符来判断是否已存在相同的请求。如果存在,则取消或忽略重复请求。
-
使用防抖(debounce)和节流(throttle)技术:这两种技术都可以用来限制函数的执行频率。防抖是在一定时间间隔内只执行一次函数,而节流是在一定时间间隔内最多执行一次函数。这两种技术可以有效防止用户频繁触发事件导致的重复请求。
-
取消未完成的请求:在发送新的请求之前,可以检查是否存在未完成的请求。如果存在,则取消这些请求,以避免重复发送。这通常可以通过使用Promise、AbortController等技术实现。
-
前端状态管理:使用状态管理工具(如Redux、Vuex等)来管理请求状态。在发送请求前,检查状态以确定是否已存在相同的请求。这种方案可以更加灵活地控制请求的行为。
-
后端接口设计:虽然前端可以采取措施防止重复请求,但后端接口的设计也非常重要。例如,可以为接口设置幂等性,确保即使多次调用接口也不会产生副作用。此外,还可以使用令牌(token)等机制来限制请求的重复发送。
综合使用这些方案,可以有效地防止前端接口的重复请求,提高用户体验和系统的稳定性。
二、取消未完成的请求
1、Axios内置的 axios.CancelToken
import type { AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
import axios from 'axios'const CancelToken = axios.CancelToken
const queue: any = [] // 请求队列const service = axios.create({baseURL: '/api',timeout: 10 * 60 * 1000,headers: {'Content-Type': 'application/json;charset=UTF-8',},
})// 取消重复请求
const removeRepeatRequest = (config: AxiosRequestConfig) => {for (const key in queue) {const index = +keyconst item = queue[key]if (item.url === config.url &&item.method === config.method &&JSON.stringify(item.params) === JSON.stringify(config.params) &&JSON.stringify(item.data) === JSON.stringify(config.data)) {// 执行取消操作item.cancel('操作太频繁,请稍后再试')queue.splice(index, 1)}}
}// 请求拦截器
service.interceptors.request.use((config: InternalAxiosRequestConfig) => {removeRepeatRequest(config)config.cancelToken = new CancelToken(c => {queue.push({url: config.url,method: config.method,params: config.params,data: config.data,cancel: c,})})return config},error => {return Promise.reject(error)}
)// 响应拦截器
service.interceptors.response.use((response: AxiosResponse): any => {removeRepeatRequest(response.config)return Promise.resolve(response)},error => {return Promise.reject(error)}
)export default service
2、发布订阅方式
💡灵感来源: 前端接口防止重复请求实现方案
/** @Author: LYM* @Date: 2024-03-28 14:12:54* @LastEditors: LYM* @LastEditTime: 2024-03-28 14:56:44* @Description: 封装axios*/
import { gMessageError, gMessageWarning, gMessageSuccess } from '@/plugins/naiveMessage'
import type { AxiosRequestConfig, AxiosResponse } from 'axios'
import axios from 'axios'
import { ContentTypeEnum } from './httpEnum'
import { checkResponseHttpStatus, loginStatusExpiresHandler } from './statusHandler'
import type { IRequestOptions, IResult } from './types'const baseURL = import.meta.env.VITE_USER_BASE_URLlet isRefreshing: boolean = false
let retryRequests: any[] = []// 发布订阅
class EventEmitter {[x: string]: {}constructor() {this.event = {}}on(type: string | number, cbres: any, cbrej: any) {if (!this.event[type]) {this.event[type] = [[cbres, cbrej]]} else {this.event[type].push([cbres, cbrej])}}emit(type: string | number, res: any, ansType: string) {if (!this.event[type]) returnelse {this.event[type].forEach((cbArr: ((arg0: any) => void)[]) => {if (ansType === 'resolve') {cbArr[0](res)} else {cbArr[1](res)}})}}
}// 根据请求生成对应的key
const generateReqKey = (config: { method: string; url: string; params: string; data: string },hash: string
) => {const { method, url, params, data } = configreturn [method, url, JSON.stringify(params), JSON.stringify(data), hash].join('&')
}// 判断是否为上传请求
const isFileUploadApi = (config: { data: any }) => {return Object.prototype.toString.call(config.data) === '[object FormData]'
}// 存储已发送但未响应的请求
const pendingRequest = new Set()
// 发布订阅容器
const ev = new EventEmitter()const service = axios.create({baseURL: import.meta.env.VITE_BASE_URL,timeout: 10 * 60 * 1000,headers: {'Content-Type': ContentTypeEnum.FORM_URLENCODED,},
})// 请求拦截器
service.interceptors.request.use(async (config: any) => {const hash = location.hash// 生成请求Keyconst reqKey = generateReqKey(config, hash)if (!isFileUploadApi(config) && pendingRequest.has(reqKey)) {// 如果是相同请求,在这里将请求挂起,通过发布订阅来为该请求返回结果// 这里需注意,拿到结果后,无论成功与否,都需要return Promise.reject()来中断这次请求,否则请求会正常发送至服务器let res = nulltry {// 接口成功响应res = await new Promise((resolve, reject) => {ev.on(reqKey, resolve, reject)})return Promise.reject({type: 'limitResSuccess',val: res,})} catch (limitFunErr) {// 接口报错return Promise.reject({type: 'limitResError',val: limitFunErr,})}} else {// 将请求的key保存在configconfig.pendKey = reqKeypendingRequest.add(reqKey)}return config},error => {return Promise.reject(error)}
)// 响应拦截器
service.interceptors.response.use((response: AxiosResponse): any => {const res = response.data || {}// 将拿到的结果发布给其他相同的接口handleSuccessResponse_limit(response)switch (res.code) {case 206:// 旧密码不正确breakcase 401:// 业务系统未登录,调用login接口登录return loginStatusExpiresHandler(response, request, service)case 402:// 登录失败gMessageWarning({content: '登录失败,请联系管理员',})breakcase 403:// 无权限,跳转到无权限页面gMessageWarning({content: res.msg || '权限不足',})breakcase 404:// 获取csrfToken,重新释放请求if (res.msg === '丢失服务器端颁发的CSRFTOKEN' ||res.msg === '请求中请携带颁发的CSRFTOKEN') {if (!isRefreshing) {isRefreshing = true// 请求tokenrequest({ url: '/csrfToken', baseURL }).then((data: any) => {if (data.code === 200) {// 遍历缓存队列 发起请求 传入最新tokenretryRequests.forEach(cb => cb())// 重试完清空这个队列retryRequests = []}})}return new Promise(resolve => {// 将resolve放进队列,用一个函数形式来保存,等token刷新后调用执行retryRequests.push(() => {resolve(service(response.config))})})}breakcase 500:// 服务器错误gMessageError({content: '服务器错误,请联系管理员',})return}return Promise.resolve(response)},error => {const { code, message } = errorif (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1) {gMessageError({content: '接口请求超时,请刷新页面重试!',})return}const err = JSON.stringify(error)if (err && err.includes('Network Error')) {gMessageError({content: '网络异常,请检查您的网络连接是否正常!',})return}// http 状态码提示信息处理const isCancel = axios.isCancel(error)if (!isCancel) {checkResponseHttpStatus(error.response && error.response.status, message)}return handleErrorResponse_limit(error)}
)// 接口响应成功
const handleSuccessResponse_limit = (response: any) => {const reqKey = response.config.pendKeyif (pendingRequest.has(reqKey)) {let x = nulltry {x = JSON.parse(JSON.stringify(response))} catch (e) {x = response}pendingRequest.delete(reqKey)ev.emit(reqKey, x, 'resolve')delete ev.reqKey}
}// 接口响应失败
const handleErrorResponse_limit = (error: { type: string; val: any; config: { pendKey: any } }) => {if (error.type && error.type === 'limitResSuccess') {return Promise.resolve(error.val)} else if (error.type && error.type === 'limitResError') {return Promise.reject(error.val)} else {const reqKey = error.config.pendKeyif (pendingRequest.has(reqKey)) {let x = nulltry {x = JSON.parse(JSON.stringify(error))} catch (e) {x = error}pendingRequest.delete(reqKey)ev.emit(reqKey, x, 'reject')delete ev.reqKey}}return Promise.reject(error)
}export default serviceexport const request = (config: AxiosRequestConfig, options?: IRequestOptions) => {return new Promise((resolve, reject) => {service(config).then((response: AxiosResponse<IResult>) => {// 返回原始数据 包含http信息if (options?.isReturnNativeResponse) {resolve(response)}// 返回的接口信息const msg = response.data.msg// 是否显示成功信息if (options?.isShowSuccessMessage) {gMessageSuccess({content: options.successMessageText ?? msg ?? '操作成功',})}if (options?.isShowErrorMessage) {gMessageError({content: options.errorMessageText ?? msg ?? '操作失败',})}resolve(response.data)}).catch(error => {reject(error)})})
}
httpEnum.ts
/*** @description: ContentType类型*/
export enum ContentTypeEnum {// jsonJSON = 'application/json;charset=UTF-8',// jsonTEXT = 'text/plain;charset=UTF-8',// form-data 一般配合qsFORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8',// form-data 上传FORM_DATA = 'multipart/form-data;charset=UTF-8',
}/*** @description: 请求方法*/
export enum MethodEnum {GET = 'GET',POST = 'POST',PATCH = 'PATCH',PUT = 'PUT',DELETE = 'DELETE',
}
naiveMessage.ts 基于naive-ui分装提示
/** @Author: LYM* @Date: 2023-03-28 08:47:39* @LastEditors: LYM* @LastEditTime: 2023-04-25 08:58:25* @Description: naive message提示*/
import { createDiscreteApi, lightTheme, type ConfigProviderProps } from 'naive-ui'
import { computed } from 'vue'
import { IconWarningFill, IconInfoFill, IconCircleCloseFilled, IconSuccessFill } from '@/icons'const configProviderPropsRef = computed<ConfigProviderProps>(() => ({theme: lightTheme,
}))const { message } = createDiscreteApi(['message'], {configProviderProps: configProviderPropsRef,
})// 警告
export const gMessageWarning = (params?: any) => {const {content = '这是一条message warning信息!',icon = IconWarningFill,duration = 5000,} = params || {}message.warning(content, {icon: () => h(icon, null),duration,})
}// 成功
export const gMessageSuccess = (params?: any) => {const {content = '这是一条message success信息!',icon = IconSuccessFill,duration = 5000,} = params || {}message.success(content, {icon: () => h(icon, null),duration,})
}// 失败
export const gMessageError = (params?: any) => {const {content = '这是一条message error信息!',icon = IconCircleCloseFilled,duration = 5000,} = params || {}message.error(content, {icon: () => h(icon, null),duration,})
}// 信息
export const gMessageInfo = (params?: any) => {const {content = '这是一条message info信息!',icon = IconInfoFill,duration = 5000,} = params || {}message.info(content, {icon: () => h(icon, null),duration,})
}// loading
export const gMessageLoading = (params?: any) => {const { content = '这是一条message Loading信息!', duration = 5000 } = params || {}message.loading(content, {duration,})
}const gMessageObj = {info: {icon: IconInfoFill,},warning: {icon: IconWarningFill,},success: {icon: IconSuccessFill,},error: {icon: IconCircleCloseFilled,},
}// 合并
export const gMessage = (params?: any) => {const { content = '这是一条message信息!', duration = 5000, type = 'info' } = params || {}message.create(content, {duration,type,icon: () => h(gMessageObj[type], null),})
}
checkResponseHttpStatus请求状态码收集处理---自行分装
loginStatusExpiresHandler登录过期或者token失效收集处理---自行分装
注意: 心跳、轮询等请求可以在入参中透传随机key值解决
相关文章:
基于Axios封装请求---防止接口重复请求解决方案
一、引言 前端接口防止重复请求的实现方案主要基于以下几个原因: 用户体验:重复发送请求可能导致页面长时间无响应或加载缓慢,从而影响用户的体验。特别是在网络不稳定或请求处理时间较长的情况下,这个问题尤为突出。 服务器压力…...
深入理解指针(7)函数指针变量及函数数组(文章最后放置本文所有原码)
一、函数指针变量 什么是函数指针变量呢? 既然是指针变量,那么它指向的一定是地址,而且我们可以通过地址来调用函数的。 函数是否有地址呢?地址是什么? 经过上面的测试可以看到函数也是有地址的,而且其地…...
office办公技能|word中的常见使用问题解决方案2.0
一、设置多级列表将表注从0开始,设置为从1开始 问题描述:word中插入题注,出来的是表0-1,不是1-1,怎么办? 写论文时,虽然我设置了“第一章”为一级标题,但是这三个字并不是自动插入的…...
华为2023年年度报告启示:大学生如何把握未来科技趋势,规划个人发展路径
华为2023年年度报告展现了公司在技术创新、生态构建、社会责任等方面的卓越成就与前瞻布局。对于身处数字化时代的大学生而言,这份报告不仅是洞察科技行业发展趋势的窗口,更是规划个人学业与职业道路的重要参考。本文将从报告中提炼关键信息,…...
刚刚,璞华科技、璞华易研PLM产品荣获智能制造领域两大奖项!
刚刚,在e-works数字化企业网于北京举办的“第十三届中国智能制造高峰论坛暨第二十一届中国智能制造岁末盘点颁奖典礼”上,璞华科技凭借在智能制造领域的雄厚实力和产品口碑,荣获两大奖项。 璞华科技被评为e-works【2023年度智能制造优秀供应…...
乐维更改IP地址
1.1 系统IP调整 vim /etc/sysconfig/network-scripts/ifcfg-ens1921.2 Web相关服务IP变更 1.2.1 编辑/itops/nginx/html/lwjkapp/.env文件,更改ZABBIXSERVER、ZABBIXRPCURL、DB_HOST中的IP 1.2.2 进入/itops/nginx/html/lwjk_app/目录下,执行php bin/manager process-conso…...
大话设计模式之简单工厂模式
简单工厂模式(Simple Factory Pattern)是一种创建型设计模式,属于工厂模式的一种。在简单工厂模式中,有一个工厂类负责根据输入参数的不同来创建不同类的实例。 简单工厂模式包含以下几个要素: 1. **工厂类࿰…...
设计模式之单例模式精讲
UML图: 静态私有变量(即常量)保存单例对象,防止使用过程中重新赋值,破坏单例。私有化构造方法,防止外部创建新的对象,破坏单例。静态公共getInstance方法,作为唯一获取单例对象的入口…...
论文复现3:Stable Diffusion v1
abstract: 通过将图像形成过程分解为去噪自动编码器的顺序应用,扩散模型 (DM) 在图像数据及其他方面实现了最先进的合成结果。此外,他们的公式允许一种指导机制来控制图像生成过程,而无需重新训练。然而,由于这些模型通常直接在像素空间中运行,因此强大的 DM 的优化通常会…...
Halcon与VisionMaster对比
作为一个经验丰富的机器视觉算法工程师,我对于机器视觉软件的评价会基于多年的实践经验和对不同软件功能的深入了解。在评价VisionMaster和Halcon软件时,我会从使用场景、工作效率、使用便捷性等方面进行全面分析,并结合软件的优缺点进行讨论…...
多线程的学习1
多线程 线程是操作系统能够进入运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。 进程:是程序的基本执行实体。 并发:在同一个时刻,有多个指令在单个CPU上交替执行。 并行:在同一时刻,…...
警务数据仓库的实现
目录 一、SQL Server 2008 R2(一)SQL Server 的服务功能(二)SQL Server Management Studio(三)Microsoft Visual Studio 二、创建集成服务项目三、配置“旅馆_ETL”数据流任务四、配置“人员_ETL”数据流任…...
Excel·VBA数组分组问题
看到一个帖子《excel吧-数据分组问题》,对一组数据分成4组,使每组的和值相近 目录 代码思路1,分组形式、可分组数代码1代码2代码2举例 2,数组所有分组形式举例 这个问题可以转化为2步:第1步,获取一组数据…...
【笔记】Hbase基础笔记
启动hbase:进入hbase安装目录 输入bin/start-hbase.sh 打开shell命令行模式:进入hbase安装目录 输入bin/hbase shell 退出shell命令行模式:exit 停止hbase:进入hbase安装目录 输入bin/stop-hbase.sh 启动关闭Hadoop和HBase的顺序一…...
创建vue3项目并集成cesium插件运行
创建vue3项目并集成cesium插件 一、vue项目创建 1、前期准备 node.js&npm或yarn 本地开发环境已经安装好。 参考安装 2、安装vue-cli,要求3以上版本 #先查看是否已经安装 vue -V#安装 npm install -g vue/cli4.5.17 示例:Idea工具 页面 Termin…...
Mac 装 虚拟机 vmware、centos7等
vmware: https://www.vmware.com/products/fusion.html centos7 清华镜像: 暂时没有官方的 m1 arm架构镜像 centos7 链接: https://pan.baidu.com/s/1oZw1cLyl6Uo3lAD2_FqfEw?pwdzjt4 提取码: zjt4 复制这段内容后打开百度网盘手机App,操…...
工厂能耗管控物联网解决方案
工厂能耗管控物联网解决方案 工厂能耗管控物联网解决方案是一种创新的、基于先进技术手段的能源管理系统,它深度融合了物联网(IoT)、云计算、大数据分析以及人工智能等前沿科技,以实现对工业生产过程中能源消耗的实时监测、精确计…...
中间件学习
一、ES 场景:某头部互联⽹公司的好房业务,双⼗⼀前⼀天,维护楼盘的运营⼈员突然接到合作开发商的通知,需要上线⼀批热⻔的楼盘列表,上传完成后,C端⼩程序⽀持按楼盘的名称、户型、⾯积等产品属性全模糊搜索…...
iOS开发进阶(十一):ViewController 控制器详解
文章目录 一、前言二、UIViewController三、UINavigationController四、UITabBarController五、UIPageViewController六、拓展阅读 一、前言 iOS 界面开发最重要的首属ViewController和View,ViewController是View的控制器,也就是一般的页面,…...
修改mysql密码
1.在此处文件夹下打开cmd 2.输入命令mysqladmin -uroot -p旧密码 password 新密码 3.在navicat进行测试连接...
uniapp 使用命令行创建vue3 ts 项目
命令行创建 uni-app 项目: vue3 ts 版 npx degit dcloudio/uni-preset-vue#vite-ts 项目名称注意 Vue3/Vite版要求 node 版本^14.18.0 || >16.0.0 如果下载失败,请去gitee下载 https://gitee.com/dcloud/uni-preset-vue/repository/archive/vite-ts…...
一周学会Django5 Python Web开发-Django5模型定义
锋哥原创的Python Web开发 Django5视频教程: 2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 Django5 Python web开发 视频教程(无废话版) 玩命更新中~共计41条视频,包括:2024版 Django5 Python we…...
kingbaseESV8逻辑备份还原
数据库逻辑备份还原 sys_dump -h127.0.0.1 -Usystem -f/home/kingbase/db01.dmp db01 ksql -h127.0.0.1 test system -c drop database db01 ksql -h127.0.0.1 test system -c create database db01 ksql -h127.0.0.1 -Usystem -ddb01 -f/home/kingbase/db01.dmp --------…...
FreeRtos作业1
1.总结keil5下载代码和编译代码需要注意的事项 代码写完之后的操作流程 2.总结STM32Cubemx的使用方法和需要注意的事项 选择芯片型号 生成代码 3.总结STM32Cubemx配置GPIO的方法 4、使用定时器2让黄灯闪烁 /* USER CODE END Header */ /* Includes --------------------------…...
spring boot dynamic 动态数据数据源配置连接池
前言 我们可以使用 dynamic-datasource 来快速实现多数据源,但是多数据源配置连接池 以及说明文档都是收费的。 这里整理的连接池的配置以及配置说明 连接池配置 (druid或者 hikari 选择一个即可) 特此说明 如果配置配到了 spring.datasour…...
vue3中如何使用 watch 函数来观察响应式数据的变化
前言 在 Vue 3 中,可以使用 watch 函数来观察响应式数据的变化。这个函数可以在组件的 setup 函数中使用。watch()方法还可以实现更多复杂的功能,比如异步获取数据并在数据更新时重新渲染页面。 代码示例 1、以下是一个使用 Vue 3 watch 函数的简单示例…...
自建机房私有云吗?
大家好,我是小码哥,之前一种有没搞清楚公有云、私有云的概念,今天算是弄清楚了,这里给大家分享一下公有云、私有云的区别,以及自建机房算不算私有云! 其实私有云(Private Cloud)和公…...
解决npm init vue@latest证书过期问题:npm ERR! code CERT_HAS_EXPIRED
目录 一. 问题背景 二. 错误信息 三. 解决方案 3.1 临时解决办法 3.2 安全性考量 一. 问题背景 我在试图创建一个新的Vue.js项目时遇到了一个问题:npm init vuelatest命令出现了证书过期的错误。不过这是一个常见的问题,解决起来也简单。 二. 错误…...
缓存和缓存的常用使用场景
想象一下,一家公司在芬兰 Google Cloud 数据中心的服务器上托管一个网站。对于欧洲用户来说,加载可能需要大约 100 毫秒,但对于墨西哥用户来说,加载需要 3-5 秒。幸运的是,有一些策略可以最大限度地减少远程用户的请求延迟。 这些策略称为缓存和内容交付网络 (CDN),它们是…...
模板方法模式(继承的优雅使用)
目录 前言 UML plantuml 类图 实战代码 AbstractRoutingDataSource DynamicDataSource DynamicDataSourceContextHolder 前言 在设计类时,一般优先考虑使用组合来替代继承,能够让程序更加的灵活,但这并不意味着要完全抛弃掉继承。 …...
垫江网站建设djrckj/站长工具关键词排名怎么查
对于给出的一组数据,要生成列表有以下方法:(1):>>>l []>>>for x in range(10):l.append(x) //追加元素到列表末尾print l[0,1,2,3,4,5,6,7,8,9](2):>>>l [x for x in range(10) if x%20] //我理解为先执…...
专业的seo网站优化公司/江苏seo网络
【单选题】以下程序的输出结果是: def hub(ss, x 2.0,y 4.0): ss x * y ss 10 print(ss, hub(ss, 3…...
wordpress自定义登录页/关键词排名怎么上首页
tar格式,会打包成一个文件,可以对多个目录,或者多个文件进行打包 tar命令只是打包,不会压缩,打包前后大小是一样的 tar命令 -c //打包 -x //解压 -f //指定文件 -t //查看 tar cf 压缩后的文件名 要…...
1个服务器可以做多少个网站/线上推广的优势和好处
嵌入式开发需要一定的知识和技能储备。下面列出技能树,后续章节再一一说明。 编程语言编程语言肯定是要的,这里只讲了编程语言的语法。但是你以为只要掌握C语言就够了?太天真了,Makefile 编译肯定要的呀。Kconfig 内核也要配置的呀…...
做网站的域名/张家港seo建站
WPF 程序出现: 参数计数不匹配,未处理System.Reflection.TargetParameterCountException解决方法引用http://www.cnblogs.com/wene/p/4668830.html根据调试的实际情况显示,委托出现问题,此异常是在使用Invoke调用时,没…...
免费网站建设源码/整站优化工具
5为学校中学生选课管理这个现实问题进行数据库模式设计。根据调查分析,确定它的属性集合为:U{S#,C#,SNAME,CNAME,TEACHER,GRADE,SD}下面给出两种确定的模式设计方案:方案一:只有一个关系模式:R(S#,C#,SNAME,CNAME,TEAC…...