前端请求并发和请求覆盖的解决方法
作者:Cder
最近在开发一些大屏,涉及到比较多的数据来源,接口也没有做聚合,导致页面需要调很多接口来填充页面数据,那么就会有接口并发的问题出现,所以本文给大家介绍了如何解决前端请求并发和请求覆盖,需要的朋友可以参考下
页面太多接口并发请求会出现什么问题?
- 服务器压力会变大:大量的并发请求会导致服务器的负载增加,从而影响服务器的性能和稳定性。
- 网络拥堵:一个域名最多有 6 个并发请求(不同浏览器可能限制不一样),超过 6 个并发请求会导致网络拥堵,从而影响页面的加载速度和用户体验。
- 响应延迟:由于服务器需要处理大量的并发请求,所以响应延迟会增加,从而影响页面的响应速度和用户体验。
解决方式也有很多,比如:
- 负载均衡:分发请求到多个服务器上
- 聚合接口:将多个接口合并成一个接口,减少接口的并发请求
- CDN 内容分发:通过不同的域名进行请求,从而突破浏览器单个域名并发请求限制
以上都是基于运维和后端的角度,那么前端如何解决呢?
并发请求
思路:设置并发请求限制,用队列存放请求,每次请求会先判断是否超出设置的最大并发请求数,当请求完成后,从队列中取出下一个请求,直到队列中的请求全部完成。
export class RequestQueue { constructor(concurrency = 6) { this.concurrency = concurrency; // 设置最大并发数,默认为6 this.queue = []; // 存放请求的队列 this.current = 0; // 当前正在执行的请求数 } // 处理队列中的请求(出队) dequeue() { while (this.current < this.concurrency && this.queue.length) { this.current++; // 从队列中取出下一个请求并执行 const requestPromiseFactory = this.queue.shift(); requestPromiseFactory() .then(() => { // 成功的请求逻辑 }) .catch((error) => { // 失败 console.log(error); }) .finally(() => { this.current--; this.dequeue(); }); } } // 添加请求到队列中(入队) enqueue(requestPromiseFactory) { this.queue.push(requestPromiseFactory); this.dequeue(); } }
代码解释:
- 构造函数 (constructor):初始化了并发数 (concurrency)、请求队列 (queue) 和当前正在执行的请求数量 (current)。
- 入队方法 (enqueue):将请求添加到队列中,并立即调用 dequeue 方法开始处理队列。
- 出队方法 (dequeue):从队列中取出请求并执行。如果请求成功,执行成功逻辑;如果请求失败,捕获错误并记录。无论成功或失败,最终都会调用 finally 块来减少当前正在执行的请求数量,并继续处理下一个请求。
实际使用
const requestQueue = new RequestQueue(6); // 创建一个并发数为6的请求队列 // 模拟一个异步函数 sleep(fn) { return new Promise(resolve => { setTimeout(() => { resolve(fn); }, 2000); }); }, // 生成测试请求 const queue = [...Array(20)].map((_, i) => () => this.sleep( axios .get('/api/test' + i) .then(r => console.log(i, '成功')) .catch(e => console.log('失败', i)) ) ); // 添加请求到队列中 for (let i = 0; i < queue.length; i++) { requestQueue.enqueue(queue[i]); }
请求覆盖
场景:先后有A、B两个请求,A请求还未返回,B请求已经发起,并且B请求的结果比A先返回,那么A请求就会覆盖B请求的结果,正常要的结果是B的结果覆盖掉A请求的结果
可以用队列来维护请求的顺序,按照队列的顺序发起请求,但这有种“杀鸡用牛刀”的感觉,因为我们完全可以取消之前的请求,用最新的请求结果来赋值
可以通过以下方式解决请求覆盖的问题:
- 时序控制:定全局标识,比如数字,依次累加,每个请求响应中判断当前的标识是否 ≥ 全局标识,是则返回结果,否则不返回结果。
- 取消旧请求:发送新请求时判断是否有旧请求,有则取消旧请求,然后再发送新请求。
方法一:时序控制
let requestId = 0; // 全局标识 // 发送请求 function sendRequest() { const currentRequestId = ++requestId; // 递增全局标识 // 发起请求 axios.get('/api/data') .then(response => { // 判断当前请求是否是最新的请求(如果有新的请求那么requestId在新的请求会+1,比当前这个方法的curentRequestId的要大) if (currentRequestId >= requestId) { // 处理响应数据 console.log(response.data); } }) .catch(error => { // 处理错误 console.error(error); }); }
方法二:取消旧请求
// 通过axios的cancelToken来取消请求 let cancelToken; // 取消请求的令牌 // 发送请求 function sendRequest() { // 取消旧请求 if (cancelToken) { cancelToken.cancel(); } // 创建新的取消请求的令牌 cancelToken = axios.CancelToken.source(); // 发起请求 axios.get('/api/data', { cancelToken: cancelToken.token }) .then(response => { // 处理响应数据 console.log(response.data); }) .catch(error => { // 处理错误 console.error(error); }); }
// 自定义的取消请求函数 let lastCancel = null; let cancelable = (req, callback) => { let cb = callback; req.then(res => { cb && cb(res); }) let cancel = () => { cb = null; } return cancel; } let sendRequest() { lastCancel && lastCancel(); lastCancel = cancelable(axios.get('/api/data'), res => { console.log(res); }) }
到此这篇关于前端请求并发和请求覆盖的解决方法的文章就介绍到这了,更多相关前端请求并发和请求覆盖内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!