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

方案一、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;
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值