前端并发控制

本文讲解Promise,callback,RxJS多种方式实现并发限制

1.Promise

目前来说,Promise是最通用的方案,一般我们最先想到Promise.all,当然最好是使用新出的Promise.allsettled

下面简单介绍下二者的区别,假如存在某个请求失败时,all会整体失败,而allsettled只会让单个请求失败,对于大部分情况来说,allsettled的是更好的选择,因为allsettled更为灵活,一般来说面对这种情况,总共有三种处理方式,如下所示,all只能支持第一种,而allsettled三种都支持:

  • 整体失败

  • 最终结果,过滤失败的选项

  • 将单个失败的保留,并渲染到UI中

方法1 全部并发

直接使用Promise.all是最简单的,代码如下,然后all并没有并发控制能力,一瞬间会将全部请求发出,从而造成前面提到的浏览器卡顿问题。

这里get函数我们使用setTimeout+随机时间来模拟请求,其返回promise实例。

function gets(ids, max) {return Promise.all(ids.map(id => get(id)))
}function get(id) {return new Promise((resolve) => {setTimeout(() => { resolve({ id }) }, Math.ceil(Math.random() * 5))});
}
方法2 分批并发

你可能会想到一种分批发送的办法,将请求按max数量分成N个组,每组并行发送,这需要结合递归和Promise.all,示例代码如下:

function gets(ids, max) {let index = 0;const result = [];function nextBatch() {const batch = ids.slice(index, index + max);index += max;return Promise.all(batch.map(get)).then((res) => {result.push(...res);if (index < ids.length) {return nextBatch();}return result;});}return nextBatch();
}

这种方法的优势在于实现相对简单,容易理解。但是它的缺点是,每一批请求中的最慢的请求会决定整个批次的完成时间,这可能会导致一些批次的其他请求早早完成后需要等待,从而降低整体的并发效率。

这种方法在业务中是不太能接受的,面试中的话,也只能勉强及格。

方法3 限制并发

一个更高效的思路是使用异步并发控制,而不是简单的批处理。这种方法可以在任何时刻都保持最大数量的并发请求,而不需要等待整个批次完成。这需要我们维护一个请求池,在每个请求完成时,将下一个请求添加到请求池中,示例代码如下:

gets函数返回一个promise,在请求全部完成后,promise变为fulfilled状态;内部采用递归,每个请求成功和失败后,发送下一个请求;在最下面先发送max个请求到请求池中。

function gets(ids, max) {return new Promise((resolve) => {const res = [];let loadcount = 0;let curIndex = 0;function load(id, index) {return get(id).then((data) => {loadcount++;if (loadcount === ids.length) {res[index] = data;resolve(res);} else {curIndex++;load(ids[curIndex]);}},(err) => {res[index] = err;loadcount++;curIndex++;load(ids[curIndex]);});}for (let i = 0; i < max && i < ids.length; i++) {curIndex = i;load(ids[i], i);}});
}

2.callback

在Promise之前,js中的异步都是基于回调函数的,比如 jQuery 的 ajax,Node.js 中的 http 模块等。

茴字有多种写法,下面我们挑战一下使用callback来解决这个问题。下面我们先把get函数改造一下,基于回调函数的get如下所示:

function get(id, success, error) {setTimeout(() => success({ id }), Math.ceil(Math.random() * 5))
}

gets函数的接口也要改成回调函数,如下所示:

function gets(ids, max, success, error) {}

回调函数也是基于上面的思路,把上面的代码稍加改动即可,将其中的Promise换成callback,示例如下:

还记得前面让你想其他思路吗,还有一种结合递归和异步函数的方法,在Promise下会比这种方法更简单,但其实还是这个思路更好,Promise和callback都可以使用。

function gets(ids, max, success, error) {const res = [];let loadcount = 0;let curIndex = 0;function load(id, index) {return get(id,(data) => {loadcount++;if (loadcount === ids.length) {res[index] = data;success(res);} else {curIndex++;load(ids[curIndex]);}},(err) => {res[index] = err;loadcount++;curIndex++;load(ids[curIndex]);});}for (let i = 0; i < max && i < ids.length; i++) {curIndex = i;load(ids[i], i);}
}

 

3.RxJS 

看看RxJS,绝大部分人都不太了解RxJS,RxJS号称异步编程的lodash,对于这个问题,其代码实现会非常简单。

RxJS 是一个用于处理异步数据流的 JavaScript 库,它通过「可观察对象」(Observable)来代表随时间推移发出值的数据流。你可以使用一系列操作符(如 mapfiltermerge 等)来处理这些数据流,并通过「订阅」(subscribe)来观察并执行相关操作。RxJS 使得处理复杂的异步逻辑变得简单而优雅,特别适合于实现并发控制等场景。

上面是RxJS的简介,相信看完了还是不理解,RxJS其实是比较难学的,建议大家阅读其他扩展资料。

下面先用RxJS改造我们的get函数,改造完如下所示,这需要用到Observableobserver,这些都是RxJS的概念,即便不知道其含义,看代码和Promise是比较相似的。

import { Observable } from 'RxJS';function get(id) {return new Observable((observer) => {setTimeout(() => {observer.next({ id });observer.complete();}, Math.ceil(Math.random() * 5));});
}

下面我们参考Promise中的思路,依次看看在RxJS中如何实现。

方法1 全部并发

在RxJS中和Promise.all类似的功能是forkJoin,这种方法最简单,代码如下所示,和Promise.all类似,这并不满足我们的需求。

import { forkJoin } from 'RxJS';function gets(ids) {const observables = ids.map(get);return forkJoin(observables);
}
方法2 分批并发

下面来看下如何实现分批并发,在Promise中我们使用递归+Promise.all来实现的。

在RxJS中,我们使用concatMap操作符来确保这些组是依次处理的,而不是同时处理。在处理每个组时,我们使用forkJoin来并行处理组内的所有请求。最后,我们使用reduce操作符来将所有组的结果合并成一个一维数组。

如果不理解RxJS,我们单纯看代码,可以看到RxJS代码的表现性更强,通过语义化的操作符串联,就完成了Promise中很多命令式的代码。

import { from, forkJoin } from 'RxJS';
import { concatMap, reduce } from 'RxJS/operators';function gets(ids, max) {// 将ids按max分组const groups = [];for (let i = 0; i < ids.length; i += max) {groups.push(ids.slice(i, i + max));}// 使用concatMap控制组之间的串行执行,并在每一组内使用forkJoin实现并行请求// 使用reduce来收集和合并所有组的结果return from(groups).pipe(concatMap((group) => forkJoin(group.map(get))),reduce((acc, results) => acc.concat(results), []));
}
方法3 限制并发

最后我们来看看RxJS如何实现限制并发,在这个实现中,我们使用mergeMap来控制并发,并使用一个Map对象来存储每个请求的结果,其中键是ID,值是请求结果。这样,我们可以在所有请求完成后,按照原始ID数组的顺序从Map中提取结果。

示例代码如下,控制并发是RxJS支持的功能,实现就是一个参数,非常简单

function gets(ids, max) {return from(ids).pipe(mergeMap((id) => get(id).pipe(map(result => ({ id, result }))), max),reduce((acc, { id, result }) => acc.set(id, result), new Map()),map(resMap => ids.map(id => resMap.get(id))));
}

 总结

我们探讨了使用Promise,callback和RxJS的方式实现并发限制,每种方式中又介绍了三种代码思路,包括全部并发、分批并发以及限制并发。每种方法都有其适用场景和优缺点:

  • 「全部并发」适用于需要将请求分批次处理的场景,简单易懂,但可能不是最高效的方法。

  • 「分批并发」在保持一定并发度的同时,避免同时发出过多的请求,适用于需要控制资源消耗的场景。

  • 「限制并发」则结合了并发的高效性和结果顺序的一致性,适用于对结果顺序有要求的并发请求处理。

通过选择合适的方法,我们可以在保证性能的同时,满足不同场景下对并发控制的需求。

再次给大家安利RxJS,RxJS作为一个强大的响应式编程库,为我们提供了灵活而强大的工具来处理这些复杂的异步逻辑。

文章的最后,我想引申下请求层的概念,在实际项目中,请求层的设计和实现对整个应用的性能和稳定性至关重要。一个健壮的请求层不仅能够处理基本的数据请求和响应,还能够应对各种复杂的网络环境和业务需求。以下是请求层可以处理的一些常见问题:

  • 「失败和错误处理」:优雅地处理请求失败和服务器返回的错误,提升用户体验。

  • 「失败重试」:在请求失败时自动重试,增加请求的成功率。

  • 「接口降级」:在服务不可用时,提供备选方案,保证应用的基本功能。

  • 「模拟接口」:在后端服务尚未开发完成时,模拟接口响应,加速前端开发。

  • 「模拟列表接口」:模拟分页、排序等列表操作,方便前端调试和测试。

  • 「接口聚合和竞态」:合并多个接口请求,减少网络开销;处理接口请求的竞态问题,确保数据的一致性。

  • 「逻辑聚合」:将多个资源的创建和更新等操作聚合成一个请求,简化前端逻辑。

  • 「控制并发数量」:限制同时进行的请求数量,避免过度消耗资源。

  • 「前端分页」:在前端进行数据分页,减轻后端压力。

  • 「超时设置」:为每个请求设置超时时间,防止长时间等待。

通过在请求层中实现这些功能,我们可以使得前端应用更加稳定和可靠,同时也提升了用户的体验。因此,「加强请求层的建设」是每个前端项目都应该重视的一个方面。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/768584.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android Audio相关

AudioManager AudioService的Bp端&#xff0c;调用AudioManager>AudioService&#xff08;代码实现&#xff09; AudioService 继承自IAudioService.Stub&#xff0c;为Bn端 AudioSystem AudioService功能实现都依赖于AudioSystem&#xff0c;AudioService通过AudioSys…

Attention Is All You Need若如爱因斯坦的相对论,Transformer模型则堪称E=MC^2之等量公式

Transformer模型已经成为当前所有自然语言处理NLP的标配&#xff0c;如GPT&#xff0c;Bert&#xff0c;Sora&#xff0c;LLama&#xff0c;Grok等。假如《Attention Is All You Need》类比为爱因斯坦的侠义相对论&#xff0c;Transformer模型则堪称EMC^2之等量公式。 看过论文…

[BT]BUUCTF刷题第6天(3.24)

第6天 Web [极客大挑战 2019]PHP Payload&#xff1a; O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";s:3:"100";}这道题考点是网站源码备份文件泄露和PHP反序列化&#xff0c;有篇介…

SpringBoot Starter解析

conditional注解解析 介绍 基于条件的注解作用: 根据是否满足某一个特定条件决定是否创建某个特定的bean意义: Springboot实现自动配置的关键基础能力 常见的conditional注解 ConditionalOnBean: 当容器中存在某个Bean才会生效ConditionalOnMissingBean: 不存在某个Bean才会…

管理自由,体验简单,使用安全 | 详解威联通全套多用户多权限管理方案【附TS-466C产品介绍】

管理自由&#xff0c;体验简单&#xff0c;使用安全 | 详解威联通全套多用户多权限管理方案【附TS-466C产品介绍】 哈喽小伙伴们好&#xff0c;我是Stark-C~。今天我们来解决一个之前评论区多次被提及的问题--多用户权限管理。 对于我们NAS用户来说&#xff0c;基本都会面临这…

docker 本地机 互通文件

查询容器name 查询容器Id 进行传输

QTabWidget的tabbar不同方向显示 文字方向设置 图标跟随变化 实现方式 qt控件绘制原理

先来看结果图&#xff1a;&#xff08;参考博客&#xff1a;QTabWidget中tab页文本水平或垂直设置_pyqt tab_widget.settabposition(qtabwidget.west) 字体-CSDN博客&#xff09; 从图中可知&#xff0c;"普通"是qt自己的样式&#xff0c;但是很明显&#xff0c;在垂…

最新Java面试题5【2024初级】

互联网大厂面试题 1&#xff1a;阿里巴巴Java面试题 2&#xff1a;阿里云Java面试题-实习生岗 3&#xff1a;腾讯Java面试题-高级 4&#xff1a;字节跳动Java面试题 5&#xff1a;字节跳动Java面试题-大数据方向 6&#xff1a;百度Java面试题 7&#xff1a;蚂蚁金服Java…

Excel打开CSV文件中文乱码问题

Excel的数据导入功能 直接用Excel打开下载的CSV文件&#xff0c;会看到汉字乱码&#xff0c;数字显示正常。如下图所示现象。 请先正常打开一份空白的excel文件&#xff0c;将鼠标定位在第一行第一列&#xff0c;这边鼠标定位的位置将决定后续打开的csv文件在excel中展示的位置…

【Python从入门到进阶】51、电影天堂网站多页面下载实战

接上篇《50、当当网Scrapy项目实战&#xff08;三&#xff09;》 上一篇我们讲解了使用Scrapy框架在当当网抓取多页书籍数据的效果&#xff0c;本篇我们来抓取电影天堂网站的数据&#xff0c;同样采用Scrapy框架多页面下载的模式来实现。 一、抓取需求 打开电影天堂网站&…

使用ADB一键停止Android设备上所有应用程序的批处理脚本

当在 Android 设备上进行开发或测试时&#xff0c;经常需要停止某些应用程序。这可能是为了清除缓存、重新加载应用程序或测试新的应用程序行为。幸运的是&#xff0c;通过使用 ADB&#xff08;Android 调试桥&#xff09;&#xff0c;可以通过命令行轻松地停止应用程序。 以下…

[leetcode] 26. 删除有序数组中的重复项

给你一个 非严格递增排列 的数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff0c;返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。 考虑 nums 的唯一元素的数量为 k &#xff0c;你…

C语言之strsep用法实例(八十六)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

C++实现FFmpeg音视频实时拉流并播放

1.准备工作: 下载rtsp流媒体服务器rtsp-simple-server,安装go开发环境并编译 编译好后启动流媒体服务器 准备一个要推流的mp4视频文件,如db.mp4 使用ffmpeg开始推流 推流命令: ffmpeg -re -stream_loop -1 -i db.mp4 -c copy -rtsp_transport tcp -f rtsp rtsp://192.168.16…

Soot 安装和简单使用

目录 前言 一、Soot 的下载和安装 1.1 在命令行中使用 Soot 1.2 在项目中使用 Soot 二、使用 Soot 生成中间代码 (IR) 三、使用 Soot 进行 Java 类插桩 四、使用 Soot 生成控制流图 (CFG) 4.1 按语句划分的控制流程图 4.2 按基本块划分的控制流程图 五、Graphviz 工具…

Docker jupyter 容器中添加matplotlib 中文支持

本教程基于 jupyter/datascience-notebook&#xff0c;适用其他容器。 # 查看所有 Docker 容器 docker ps -a # 进入已经运行的 Jupyter 容器 docker exec -it CONTAINER_ID bash 本例中CONTAINER_ID为2e # 切换到 matplotlib 的字体目录&#xff08;find / -name "…

HTML5和CSS3新特性

Html新增属性 1.新增语义化标签 <header>&#xff1a;头部标签 <nav>&#xff1a;导航标签 <article>&#xff1a;内容标签 <section>&#xff1a;定义文档某个区域 <aside>&#xff1a;侧边栏标签 <footer>&#xff1a;尾部标签 2.…

kafka优化--来自gpt

增加Topic的分区数&#xff1a; 分区数越多&#xff0c;可以并行处理的能力越强。 配置参数&#xff1a;num.partitions 增加消费者&#xff08;Consumer&#xff09;的并行度&#xff1a; 根据硬件资源调整消费者实例的数量。 配置消费者组内的消费者实例数。 调整消费者&…

力扣hot100:994. 腐烂的橘子(多源BFS)

这是一个典型的多源BFS问题&#xff0c;如果初学数据结构的同学&#xff0c;可能第一次不能想到&#xff0c;但是如果做过一次应该就能运用了。      主要思路大概是初始时&#xff0c;多个点进入队列然后进行BFS。将某一等价集合视作同一个起始点&#xff08;超级源点&…

blender插件笔记

目录 文件拖拽导入 smpl导入导出 好像可以导入动画 smpl_blender_addon导入一帧 保存pose 导入导出完整代码 文件拖拽导入 https://github.com/mika-f/blender-drag-and-drop 支持格式&#xff1a; *.abc*.bvh*.dae*.fbx*.glb*.gltf*.obj*.ply*.stl*.svg*.usd*.usda*.…