基于Axios封装请求---防止接口重复请求解决方案

 一、引言

前端接口防止重复请求的实现方案主要基于以下几个原因:

  1. 用户体验:重复发送请求可能导致页面长时间无响应或加载缓慢,从而影响用户的体验。特别是在网络不稳定或请求处理时间较长的情况下,这个问题尤为突出。

  2. 服务器压力:如果前端不限制重复请求,服务器可能会接收到大量的重复请求,这不仅增加了服务器的处理负担,还可能导致资源浪费。

  3. 数据一致性:对于某些操作,如表单提交,重复请求可能导致数据重复插入或更新,从而破坏数据的一致性。

为了实现前端接口防止重复请求,可以采取以下方案:

  1. 设置请求标志:在发送请求时,为请求设置一个唯一的标识符(如请求ID)。在请求处理过程中,可以通过检查该标识符来判断是否已存在相同的请求。如果存在,则取消或忽略重复请求。

  2. 使用防抖(debounce)和节流(throttle)技术:这两种技术都可以用来限制函数的执行频率。防抖是在一定时间间隔内只执行一次函数,而节流是在一定时间间隔内最多执行一次函数。这两种技术可以有效防止用户频繁触发事件导致的重复请求。

  3. 取消未完成的请求:在发送新的请求之前,可以检查是否存在未完成的请求。如果存在,则取消这些请求,以避免重复发送。这通常可以通过使用Promise、AbortController等技术实现。

  4. 前端状态管理:使用状态管理工具(如Redux、Vuex等)来管理请求状态。在发送请求前,检查状态以确定是否已存在相同的请求。这种方案可以更加灵活地控制请求的行为。

  5. 后端接口设计:虽然前端可以采取措施防止重复请求,但后端接口的设计也非常重要。例如,可以为接口设置幂等性,确保即使多次调用接口也不会产生副作用。此外,还可以使用令牌(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值解决

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

树状数组与线段树基础3

本来想练练线段树的&#xff0c;没想到有许多细节忘了&#xff0c;加上今天的金工实习坐牢坐穿了&#xff0c;于是再复习一下吧。 首先介绍一下树状数组&#xff08;貌似第一篇就讲了&#xff0c;不过那个东西真是一坨Shit,当时还没有怎么理解就写了&#xff09; 首先它的复杂…

知识图谱与大数据:区别、联系与应用

目录 前言1 知识图谱1.1 定义1.2 特点1.3 应用 2 大数据2.1 定义2.2 应用 3. 区别与联系3.1 区别3.2 联系 结语 前言 在当今信息爆炸的时代&#xff0c;数据成为了我们生活和工作中不可或缺的资源。知识图谱和大数据是两个关键概念&#xff0c;它们在人工智能、数据科学和信息…

30-3 越权漏洞 - 水平越权(横向越权)

环境准备:构建完善的安全渗透测试环境:推荐工具、资源和下载链接_渗透测试靶机下载-CSDN博客 一、定义 攻击者可以访问和操作与其拥有同级权限的用户资源。 示例: 学生A在教务系统上正常只能修改自己的作业内容,但由于不合理的权限校验规则等原因,学生A可以修改学生B的内…

【python】flask执行上下文context,请求上下文和应用上下文原理解析

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

【Java - 框架 - Lombok】(2) SpringBoot整合Lombok完成日志的创建使用 - 快速上手;

"SpringBoot"整合"Lombok"完成日志的创建使用 - 快速上手&#xff1b; 环境 “Java"版本"1.8.0_202”&#xff1b;“Lombok"版本"1.18.20”&#xff1b;“Spring Boot"版本"2.5.9”&#xff1b;“Windows 11 专业版_22621…

厨余垃圾处理设备工业监控PLC连接APP小程序智能软硬件开发之功能原理篇

接着上一篇《厨余垃圾处理设备工业监控PLC连接APP小程序智能软硬件开发之功能结构篇》继续总结一下厨余垃圾处理设备智能软硬件统的原理。所有的软硬件系统全是自己一人独自开发&#xff0c;看法和角度难免有局限性。希望抛砖引玉&#xff0c;将该智能软硬件系统分享给更多有类…

Web APIs

文章目录 Web APIs1. DOM1.1 介绍DOM 树DOM 节点document 1.2 获取DOM对象1.3 操作元素内容1.4 操作元素属性常用属性修改控制样式属性操作表单元素属性自定义属性 1.5 间歇函数1.6 事件事件监听事件类型事件处理程序 1.7 事件类型鼠标事件键盘事件焦点事件文本框输入事件 1.8 …

数据分析之Power BI

POWER QUERY 获取清洗 POWER PIVOT建模分析 如何加载power pivot 文件-选项-加载项-com加载项-转到 POWER VIEW 可视呈现 如何加载power view 文件-选项-自定义功能区-不在功能区中的命令-新建组-power view-添加-确定 POWER MAP可视地图

vue3-pinia使用(末尾有彩蛋)

什么是 pinia Pinia 是 Vue 的专属状态管理库&#xff0c;它允许你跨组件或页面共享状态。 之前用的是 vuex&#xff0c;后面 vue 官方团队不维护了&#xff0c;推荐使用 pinia 安装 yarn add pinia # 或者使用 npm npm install piniapnpm install piniaStore 是什么&#xf…

AIGC-Stable Diffusion发展及原理总结

目录 一. AIGC介绍 1. 介绍 2. AIGC商业化方向 3. AIGC是技术集合 4. AIGC发展三要素 4.1 数据 4.2 算力 4.3 算法 4.3.1 多模态模型CLIP 4.3.2 图像生成模型 二. Stable Diffusion 稳定扩散模型 1. 介绍 1.1 文生图功能&#xff08;Txt2Img) 1.2 图生图功能&…

八大技术趋势案例(区块链量子计算)

科技巨变,未来已来,八大技术趋势引领数字化时代。信息技术的迅猛发展,深刻改变了我们的生活、工作和生产方式。人工智能、物联网、云计算、大数据、虚拟现实、增强现实、区块链、量子计算等新兴技术在各行各业得到广泛应用,为各个领域带来了新的活力和变革。 为了更好地了解…

C语言中如何动态分配内存并进行操作

C语言文章更新目录 C语言学习资源汇总&#xff0c;史上最全面总结&#xff0c;没有之一 C/C学习资源&#xff08;百度云盘链接&#xff09; 计算机二级资料&#xff08;过级专用&#xff09; C语言学习路线&#xff08;从入门到实战&#xff09; 编写C语言程序的7个步骤和编程…

设计模式之单例模式精讲

UML图&#xff1a; 静态私有变量&#xff08;即常量&#xff09;保存单例对象&#xff0c;防止使用过程中重新赋值&#xff0c;破坏单例。私有化构造方法&#xff0c;防止外部创建新的对象&#xff0c;破坏单例。静态公共getInstance方法&#xff0c;作为唯一获取单例对象的入口…

港大新工作 HiGPT:一个模型,任意关系类型 !

论文标题&#xff1a; HiGPT: Heterogeneous Graph Language Model 论文链接&#xff1a; https://arxiv.org/abs/2402.16024 代码链接&#xff1a; https://github.com/HKUDS/HiGPT 项目网站&#xff1a; https://higpt-hku.github.io/ 1. 导读 异质图在各种领域&#xf…

Polar靶场web(三)

期待得到某一件事物的时候&#xff0c;才是最美好的。 签到 发现不能提交&#xff0c;看一下f12 发现提交按钮被禁用了&#xff0c;且最大输入9个字符&#xff0c;我们可以改一下。 现随便提交一个发现要提交ilovejijcxy session文件包含 发现有文件包含&#xff0c;那先包含…

基于单片机的便携式瓦斯检测仪系统设计

**单片机设计介绍&#xff0c;基于单片机的便携式瓦斯检测仪系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机的便携式瓦斯检测仪系统设计是一个针对煤矿等工业环境中瓦斯气体浓度检测的重要项目。以下是该设计…

智慧城市一屏统览,数字孪生综合治理

现代城市作为一个复杂系统&#xff0c;牵一发而动全身&#xff0c;城市化进程中产生新的矛盾和社会问题都会影响整个城市系统的正常运转。智慧城市是应对这些问题的策略之一。城市工作要树立系统思维&#xff0c;从构成城市诸多要素、结构、功能等方面入手&#xff0c;系统推进…

工厂能耗管控物联网解决方案

工厂能耗管控物联网解决方案 工厂能耗管控物联网解决方案是一种创新的、基于先进技术手段的能源管理系统&#xff0c;它深度融合了物联网&#xff08;IoT&#xff09;、云计算、大数据分析以及人工智能等前沿科技&#xff0c;以实现对工业生产过程中能源消耗的实时监测、精确计…

COLMAP(Windows)实现SFM三维重建位姿估计

问题产生&#xff1a; Guassian splatting第一步用colmap进行位姿估计&#xff0c;图片匹配失败&#xff0c;输出图片全靠运气&#xff0c;最少的时候甚至一张都没匹配上&#xff0c;所以想到用colmap软件先进行匹配&#xff0c;再放入高斯训练。 colmap使用步骤&#xff1a;…

代码随想录-DAY4|leetcode-24,19,142,面试题 02.07

文章目录 22. 两两交换链表中的节点19. 删除链表的倒数第N个节点size-n方式删除双指针方式&#xff08;推荐&#xff09; 面试题 02.07. 链表相交142. 环形链表II暴力解法快慢指针&#xff08;推荐&#xff09; 22. 两两交换链表中的节点 leetcode链接&#xff1a;两两交换链表…