前端:防止重复请求的方案

devtools/2024/9/24 10:22:20/

前端:防止重复请求的方案

  • 方案一、axios请求拦截器
  • 方案二、把相同的请求拦截掉
  • 方案三、(最佳推荐)

方案一、axios请求拦截器

通过使用 axios拦截器,在请求拦截器中开启全屏Loading,然后再响应拦截器中关闭。

javascript">import axios from "axios";
import { ElLoading } from "element-plus"let instance = axios.create({baseURL = "/api/"
})
let loadingInstance = null;
instance.interceptors.request.use((config)=>{loadingInstance = ElLoading.service({fullscreen:true,background:"rgba(0,0,0,0.7)"});return config;
},(error)=>{return Promise.reject(error);
})
instance.interceptors.response.use((response)=>{loadingInstance = close(0);return response;
},(error)=>{return Promise.reject(error);
})
export default instance;

缺点:全屏Loading不适合所有请求,不美观

方案二、把相同的请求拦截掉

1.判断什么样的请求属于相同的请求?
请求方法、地址、参数、页面hash,可以根据这几个数据来生成一个key作为请求的标识

javascript">// 根据请求生成对应的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();// 添加请求拦截器
instance.interceptors.request.use((config)=>{let hash = location.hash;// 生成请求keylet reqKey = generateReqKey (config,hash);if(pendingRequest.has(reqKey )){return Promise.reject();} else {// 将请求的key保存在configconfig.pendKey = reqKey;pendingRequest.add(reqKey);}return config;
},(error)=>{return Promise.reject(error);
})
// 添加响应拦截器
instance.interceptors.response.use((response)=>{// 从config中取出请求key,并从集合中删除pendingRequest.delete(response.config.pendKey);return response;
},(error)=>{pendingRequest.delete(error.config.pendKey);return Promise.reject(error);
})
export default instance;

2、缺点:会导致多次报error消息提示,很不友好;如果在error中有更多的逻辑处理,会导致整个程序的异常;若两个请求来自同一个页面(一个页面里面的两个组件都需要调用同一个接口时),后调的接口的组件无法拿到正确的数据。

方案三、(最佳推荐)

延续方案二的思路,对于相同的请求我们先给它挂起,等到最先发出的请求拿回结果后,把成功或失败的结果共享到后面到来的相同的请求。
1、挂起请求时,要用到发布订阅模式
2、对于挂起的请求,一定要在请求拦截器中通过return Promise.reject()来直接中断请求,并做一些特殊标记,以便于在响应拦截器中进行特殊处理

javascript">import axios from "axios";let instance = axios.create({baseURL = "/api/"
})
// 发布订阅
class EventeEmitter {constructor(){this.event={};}on(type,cbres,cbrej){if(!this.event[type]){this.event[type]=[[cbres,cbrej]];}else{this.event.push([cbres,cbrej]);}}emit(type,res,ansType){if(!this.event[type])return;else{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)=>{if(isFileUploadApi(config) ) return;let hash = location.hash;// 生成请求keylet reqKey = generateReqKey (config,hash);if(pendingRequest.has(reqKey )){// 如果请求相同,在这里将请求挂起,通过发布订阅来为该请求返回// 在这里注意,拿到结果后,无论成功与否,都需要return Promise(),
// 则请求会正常发送到服务器。let res = null;try {// 接口成功响应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 = reqKey;pendingRequest.add(reqKey);}return config;
},(error)=>{return Promise.reject(error);
})
// 添加响应拦截器
instance.interceptors.response.use((response)=>{//将拿到的结果发布给其他相同的接口handleSuccessResponse_limit(response);return response;
},(error)=>{return handleErrorResponse_limit(error);
})
// 接口响应成功
function handleSuccessResponse_limit(response){const reqKey = response.config.pendKey;if(pendingRequest.has(reqKey )){let x=null;try{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 === "limitResSuccess"){return Promise.resolve(error.val)}else if(error.type && error.type === "limitResError"){return Promise.rejct(error.val)}else{const reqKey = error.config.pendKey;if(pendingRequest.has(reqKey )){let x=null;try{x=JSON.parse(JSON.stringify(error));}catch(e){x=response;}pendingRequest.delete(reqKey);ev.emit(reqKey,x,"resolve");delete ev.reqKey;}}return Promise.reject(error);
}
// 判断是否是文件类型
function isFileUploadApi(config){return Object.prototype.toString.call(config.data)==="[object FormData]";
}
export default instance;

http://www.ppmy.cn/devtools/9591.html

相关文章

linux之进程通信

目录 一、进程通信介绍 1.目的 2.发展 3.进程通信是什么,怎么通信? 二、管道 1.介绍 2.匿名管道 1.单向通信管道原理 2.代码实现 3.管道特征 4.管道的四种情况 5.管道的应用场景 使用管道实现一个简易版本的进程池 3.命名管道 1.思考 2.…

24年上半年值得上车的CPU,个个都能闭眼入

最近 Intel 和 AMD 红蓝两厂算是打的如火如荼,不过对咱消费者来说这是好事儿,厂家打架,消费者得利,虽然硬盘涨价了,但好在它在装机预算里边儿占比不大,咱该装还得装。 那咱们今天就来盘点下目前比较值得入…

路由过滤,路由策略小实验

目录 一,实验拓扑: 二,实验要求: 三,实验思路: 四,实验过程: 1,IP配置: 2、R1 和R2 运行 RIPv2,R2,R3 和R4运行 oSPF&#xff0…

JavaScript零基础进阶2024详解

数组 JS中的数组相当于Java中的集合 数组长度可变 元素可变 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><…

Grass注册不了、按钮灰色的解决方案

近期相信grass挂机项目不少人有所有接触。还有不了解这个项目的可以看看博客&#xff1a; http://t.csdnimg.cn/bI4UO 但是不少人注册时遇到无法注册的问题&#xff0c;或者是注册按钮显示灰色&#xff0c;放上鼠标时显示禁止。这也是博主在尝试时遇到的问题。 经过探索&…

如何防止用户手动填写身份证信息来作弊?

当前有不少的网站或企业需要用户采用身份证读卡器远程在程序里面填写身份证信息&#xff0c;用于核实用户的真实身份&#xff0c;以完成某些业务&#xff0c;但是&#xff0c;有些用户为了达到不可告人的目的&#xff0c;会采用作弊手段&#xff0c;绕过机器采集这一关&#xf…

Go 之 sync.Mutex 加锁失效现象

我先声明一下&#xff0c;并不是真的加锁失效&#xff0c;而是我之前的理解有误&#xff0c;导致看起来像是加锁失效一样。于是乎记录一下&#xff0c;加深一下印象。 我之前有个理解误区&#xff08;不知道大家有没有&#xff0c;有的话赶紧纠正一下——其实也是因为我这块的…

算法打卡day37

今日任务&#xff1a; 1&#xff09;1049. 最后一块石头的重量 II 2&#xff09;494. 目标和 3&#xff09;474.一和零 4&#xff09;复习day12 1049. 最后一块石头的重量 II 题目链接&#xff1a;1049. 最后一块石头的重量 II - 力扣&#xff08;LeetCode&#xff09; 题目难…