【前端】-【防止接口重复请求】

文章目录

  • 需求
  • 实现方案
    • 方案一
    • 方案二
    • 方案三

需求

对整个的项目都做一下接口防止重复请求的处理

实现方案

方案一

思路:通过使用axios拦截器,在请求拦截器中开启全屏Loading,然后在响应拦截器中将Loading关闭。
代码:
在这里插入图片描述
问题:在目前项目的接口处理逻辑中还有一些局部Loading,就有可能会出现Loading套Loading的情况

方案二

思路:对于同一个接口,如果传参都是一样的,一般来说都没有必要连续请求多次吧。那我们可以通过代码逻辑直接把完全相同的请求给拦截掉,不让它到达服务端
代码:

  1. 如果两个请求的请求方法,地址,参数以及请求发出的页面hash(如果是history路由,可以将pathname加入生成key)都一样,那么可以认为他们是相同请求,我们可以根据这几个数据把这个请求生成一个key来作为这个请求的唯一标识
// 根据请求生成对应的key
function generateReqKey(config, hash) {const { method, url, params, data } = config;return [method, url, JSON.stringify(params), JSON.stringify(data), hash].join("&");
}
  1. 有了请求的key,我们就可以在请求拦截器中把每次发起的请求给收集起来,后续如果有相同请求进来,那都去这个集合中去比对,如果已经存在了,说明就是一个重复的请求,我们就给拦截掉。当请求完成响应后,再将这个请求从集合中移除。
    在这里插入图片描述
    缺点:假如项目中会有一些数据字典型的接口,这些接口由不同页面调用,如果第一个页面请求的字典接口比较慢,第二个页面的接口就被拦截了,最后就会导致第二个页面逻辑错误。虽然我们生成key的时候加入了hash(不会存在不同页面调用相同数据字典型接口,后面的请求被拦截的情况),但如果我这两个请求是来自同一个页面呢?比如,一个页面同时加载两个组件,而这两个组件都需要调用某个接口时:
    在这里插入图片描述
    那么此时,后调接口的组件就无法拿到正确数据了

方案三

思路:延续方案二的思路,仍然是拦截相同请求,但这次我们不直接把请求挂掉,而是对于相同的请求我们先给它挂起,等到最先发出去的请求拿到结果回来之后,把成功或失败的结果共享给后面到来的相同请求。
在这里插入图片描述
需要注意的是:

  1. 在拿到响应结果后,返回给之前我们挂起的请求时,我们要用到发布订阅模式
  2. 对于挂起的请求,我们需要将它拦截,不能让它执行正常的请求逻辑,所以一定要在请求拦截器中通过return Promise.reject()来直接中断请求,并做一些特殊的标记,以便于在响应拦截器中进行特殊处理。
import axios from "axios"let instance = axios.create({baseURL: "/api/"
})// 发布订阅
class EventEmitter {constructor() {this.event = {}}on(type, cbres, cbrej) {if (!this.event[type]) {this.event[type] = [[cbres, cbrej]]} else {this.event[type].push([cbres, cbrej])}}emit(type, res, ansType) {if (!this.event[type]) returnelse {this.event[type].forEach(cbArr => {if(ansType === 'resolve') {cbArr[0](res)}else{cbArr[1](res)}});}}
}// 根据请求生成对应的key
function generateReqKey(config, hash) {const { method, url, params, data } = config;return [method, url, JSON.stringify(params), JSON.stringify(data), hash].join("&");
}// 存储已发送但未响应的请求
const pendingRequest = new Set();
// 发布订阅容器
const ev = new EventEmitter()// 添加请求拦截器
instance.interceptors.request.use(async (config) => {let hash = location.hash// 生成请求Keylet reqKey = generateReqKey(config, hash)if(pendingRequest.has(reqKey)) {// 如果是相同请求,在这里将请求挂起,通过发布订阅来为该请求返回结果// 这里需注意,拿到结果后,无论成功与否,都需要return Promise.reject()来中断这次请求,否则请求会正常发送至服务器let res = nulltry {// 接口成功响应res = await new Promise((resolve, reject) => {ev.on(reqKey, resolve, reject)})return Promise.reject({type: 'limiteResSuccess',val: res})}catch(limitFunErr) {// 接口报错return Promise.reject({type: 'limiteResError',val: limitFunErr})}}else{// 将请求的key保存在configconfig.pendKey = reqKeypendingRequest.add(reqKey)}return config;}, function (error) {return Promise.reject(error);});// 添加响应拦截器
instance.interceptors.response.use(function (response) {// 将拿到的结果发布给其他相同的接口handleSuccessResponse_limit(response)return response;}, function (error) {return handleErrorResponse_limit(error)});// 接口响应成功
function handleSuccessResponse_limit(response) {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}
}// 接口走失败响应
function handleErrorResponse_limit(error) {if(error.type && error.type === 'limiteResSuccess') {return Promise.resolve(error.val)}else if(error.type && error.type === 'limiteResError') {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 instance;

问题:当上传了两个不同的文件时,只调用了一次上传接口,按理说是两个不同的请求,可为什么会被我们前面写的逻辑给拦截掉一个呢?我们打印一下请求的config:
在这里插入图片描述
可以看到,请求体data中的数据是FormData类型,而我们在生成请求key的时候,是通过JSON.stringify方法进行操作的,而对于FormData类型的数据执行该函数得到的只有{}。所以,对于文件上传,尽管我们上传了不同的文件,但它们所发出的请求生成的key都是一样的,这么一来就触发了我们前面的拦截机制。那么我们接下来我们只需要在我们原来的拦截逻辑中判断一下请求体的数据类型即可,如果含有FormData类型的数据,我们就直接放行不再关注这个请求就是了。

function isFileUploadApi(config) {return Object.prototype.toString.call(config.data) === "[object FormData]"
}

react直接用swr,ahook
vue用VueRequest

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

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

相关文章

C# Winform父窗体打开新的子窗体前,关闭其他子窗体

随着Winform项目越来越多,界面上显示的窗体越来越多,窗体管理变得更加繁琐。有时候我们要打开新窗体,然后关闭多余的其他窗体,这个时候如果一个一个去关闭就会变得很麻烦,而且可能还会出现遗漏的情况。这篇文章介绍了三…

记一次生产事故的排查和解决

一. 事故概述 春节期间, 生产系统多次出现假死不可用现象, 导致绝大部分业务无法进行. 主要表现现象为接口无法访问. 背景为900W客户表和近实时ES, 以及春节期间疫情导致的普通卖菜场景近似秒杀等. 二. 排查过程 优先排查了info, error, catalina日志, 发现以下异常: 主要的…

C语言——单链表实现数据增删查改

一.前言 嗨嗨嗨,我们又见面了。前面我们已经学习了关于数据结构中的顺序表,今天我们来学习数据结构中的单链表。废话不多说让我们直接开始吧。 二.正文 1.1链表的概念 链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺…

【Flask开发实战】HTML模板代码复用之extends使用

一、前言 在 HTML 开发中,尤其是在使用模板引擎(如 Jinja2,常用于 Flask 应用)时,extends 是一个非常有用的指令,它用于实现模板继承,从而达到代码复用的目的。这可以让你定义一个基本模板&…

三体中的二向箔

目录 描述 外观 基本原理 攻击范围及影响 副作用 保存方式 实战情况 二向箔是在中国科幻名匠刘慈欣的作品《三体3:死神永生》中登场的宇宙规律武器之一。首次出现于一艘来自歌者“母世界”的宇宙飞船。由于宇宙战争愈演愈烈,二向箔对于高等文明而…

Codigger数据篇(下):数据安全的全方位保障

在数字化浪潮中,数据已成为现代企业的核心财富。Codigger作为领先的数据服务平台,深知数据安全对于用户的重要性,因此在深挖数据价值的同时,我们始终坚守数据安全防线。 一、双重加密技术保障 Codigger平台运用先进的加密通信和…

python 基础语句

python 基础语句 1. import 语句 用于导入相应的包以供后面的代码使用 import xxx -将xxx包导入import pandas as pd - 导入 pandas 包并且将其所有的对象、方法、属性赋予 pd 对象from xxx1 import xxx2 - 从xxx1 包中导入 xxx2 方法 2. 输入输出语句 input,pr…

ip ssl证书无限端口网站

IP SSL证书是由CA认证机构颁发的一种特殊数字证书。大部分SSL数字证书都需要用户使用域名进行申请,想要对公网IP地址加密实现https访问就需要申请IP SSL证书。IP SSL证书采用了强大的加密算法,可以有效地防止数据在传输过程中被窃取或篡改,具…

TS学习-泛型基础

目录 1,介绍1,在函数中使用2,在类型别名,接口中使用3,在类中使用 2,泛型约束3,多泛型4,举例实现 Map 1,介绍 泛型相当于是一个类型变量,有时无法预先知道具体…

药品商品名/通用名/化学名/别名-数据库查询方法

药品通用名-药品化学名-药品商品名的区别 药品通用名通常是由药典委员会或相关药品命名机构根据药品的化学成分或治疗特性制定的标准化名称。这个名称在科学和医疗领域中用于确保对药品的准确和一致性识别。如阿司匹林(Aspirin)是药品的通用名。&#x…

Tomact安装配置及使用(超详细)

文章目录 web相关知识概述web简介(了解)软件架构模式(掌握)BS:browser server 浏览器服务器CS:client server 客户端服务器 B/S和C/S通信模式特点(重要)web资源(理解)资源分类 URL请求路径(理解)作用介绍格式浏览器通过url访问服务器的过程 服务器(掌握)…

Vue.js课后练习(登录注册和大小比较)

第一题 请编写登录页面和注册页面&#xff0c;通过动态组件实现动态切换页面中显示的组件&#xff0c;效果如图1和图2所示。 图1 登录页面 图2 注册页面 代码&#xff1a; my.vue代码: <template>登录 </template><script setup> </script><st…