前端:防止重复请求的方案
- 方案一、axios请求拦截器
- 方案二、把相同的请求拦截掉
- 方案三、(最佳推荐)
方案一、axios请求拦截器
通过使用 axios拦截器,在请求拦截器中开启全屏Loading,然后再响应拦截器中关闭。
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作为请求的标识。
// 根据请求生成对应的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;
// 生成请求key
let reqKey = generateReqKey (config,hash);
if(pendingRequest.has(reqKey )){
return Promise.reject();
} else {
// 将请求的key保存在config
config.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()来直接中断请求,并做一些特殊标记,以便于在响应拦截器中进行特殊处理。
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;
// 生成请求key
let 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保存在config
config.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;