前端:防止重复请求的方案
- 方案一、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;// 生成请求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()来直接中断请求,并做一些特殊标记,以便于在响应拦截器中进行特殊处理。
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;