JavaScript/TypeScript 实现并发请求控制的示例代码


Posted in Javascript onJanuary 18, 2021

场景

假设有 10 个请求,但是最大的并发数目是 5 个,并且要求拿到请求结果,这样就是一个简单的并发请求控制

模拟

利用 setTimeout 实行简单模仿一个请求

let startTime = Date.now();
const timeout = (timeout: number, ret: number) => {
 return (idx?: any) =>
 new Promise((resolve) => {
  setTimeout(() => {
  const compare = Date.now() - startTime;
  console.log(`At ${Math.floor(compare / 100)}00 return`, ret);
  resolve(idx);
  }, timeout);
 });
};

const timeout1 = timeout(1000, 1);
const timeout2 = timeout(300, 2);
const timeout3 = timeout(400, 3);
const timeout4 = timeout(500, 4);
const timeout5 = timeout(200, 5);

通过这样来模拟请求,本质就是 Promise

没有并发控制的时候

const run = async () => {
 startTime = Date.now();
 await Promise.all([
 timeout1(),
 timeout2(),
 timeout3(),
 timeout4(),
 timeout5(),
 ]);
};

run();

At 200 return 5
At 300 return 2
At 400 return 3
At 500 return 4
At 1000 return 1

可以看到输出是 5 2 3 4 1 ,按 timeout 的时间输出了

并发条件

假设同时间最大并发数目是 2,创建一个类

class Concurrent {
 private maxConcurrent: number = 2;

 constructor(count: number = 2) {
 this.maxConcurrent = count;
 }
}

第一种并发控制

想一下,按最大并发数拆分 Promise 数组,如果有 Promise 被 fulfilled 的时候,就移除掉,然后把 pending 状态的 Promise ,加进来。Promise.race 可以帮我们满足这个需求

class Concurrent {
 private maxConcurrent: number = 2;

 constructor(count: number = 2) {
 this.maxConcurrent = count;
 }
 public async useRace(fns: Function[]) {
 const runing: any[] = [];
 // 按并发数,把 Promise 加进去
 // Promise 会回调一个索引,方便我们知道哪个 Promise 已经 resolve 了
 for (let i = 0; i < this.maxConcurrent; i++) {
  if (fns.length) {
  const fn = fns.shift()!;
  runing.push(fn(i));
  }
 }
 const handle = async () => {
  if (fns.length) {
  const idx = await Promise.race<number>(runing);
  const nextFn = fns.shift()!;
  // 移除已经完成的 Promise,把新的进去
  runing.splice(idx, 1, nextFn(idx));
  handle();
  } else {
  // 如果数组已经被清空了,表面已经没有需要执行的 Promise 了,可以改成 Promise.all
  await Promise.all(runing);
  }
 };
 handle();
 }
}

const run = async () => {
 const concurrent = new Concurrent();
 startTime = Date.now();
 await concurrent.useRace([timeout1, timeout2, timeout3, timeout4, timeout5]);
};

At 300 return 2
At 700 return 3
At 1000 return 1
At 1200 return 5
At 1200 return 4

可以看到输出已经变了,为什么会这样呢,分析一下,最大并发数 2

// 首先执行的是 1 2
1 需要 1000 MS 才执行完
2 需要 300 MS

2 执行完,时间线变成 300 移除 2 加入 3 开始执行 3
3 需要 400MS 执行完时间变成 700 移除 3 加入 4 开始执行 4
4 需要 500MS
时间线来到 1000MS,1 执行完 移除 1 加入 5 开始执行 5
时间线来到 1200MS,4 和 5 刚好同时执行完

第二种方案

可以利用 await 的机制,其实也是一个小技巧

await 表达式会暂停当前 async function 的执行,等待 Promise 处理完成。若 Promise 正常处理(fulfilled),其回调的 resolve 函数参数作为 await 表达式的值,继续执行 async function。

如果当前的并发数已经超过最大的并发数目了,可以设置一个新的 Promise,并且 await,等待其他的请求完成的时候,resolve,移除等待,所以需要新增两个状态,当前的并发数目,还有用来存储 resolve 这个回调函数的数组

class Concurrent {
 private maxConcurrent: number = 2;
 private list: Function[] = [];
 private currentCount: number = 0;

 constructor(count: number = 2) {
 this.maxConcurrent = count;
 }
 public async add(fn: Function) {
 this.currentCount += 1;
 // 如果最大已经超过最大并发数
 if (this.currentCount > this.maxConcurrent) {
  // wait 是一个 Promise,只要调用 resolve 就会变成 fulfilled 状态
  const wait = new Promise((resolve) => {
  this.list.push(resolve);
  });
  // 在没有调用 resolve 的时候,这里会一直阻塞
  await wait;
 }
 // 执行函数
 await fn();
 this.currentCount -= 1;
 if (this.list.length) {
  // 把 resolve 拿出来,调用,这样 wait 就完成了,可以往下面执行了
  const resolveHandler = this.list.shift()!;
  resolveHandler();
 }
 }
}

const run = async () => {
 const concurrent = new Concurrent();
 startTime = Date.now();
 concurrent.add(timeout1);
 concurrent.add(timeout2);
 concurrent.add(timeout3);
 concurrent.add(timeout4);
 concurrent.add(timeout5);
};

run();

At 300 return 2
At 700 return 3
At 1000 return 1
At 1200 return 5
At 1200 return 4

总结

这两种方式都可以实现并发控制,只不过实现的方式不太一样,主要都是靠 Promise 实现,另外实现方式里面没有考虑异常的情况,这个可以自己加上

到此这篇关于JavaScript/TypeScript 实现并发请求控制的示例代码的文章就介绍到这了,更多相关JavaScript 并发请求控制内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
jQuery checkbox全选/取消全选实现代码
Nov 14 Javascript
jQuery EasyUI API 中文文档 - Menu菜单
Oct 03 Javascript
Javascript获取窗口(容器)的大小及位置参数列举及简要说明
Dec 09 Javascript
jquery中通过父级查找进行定位示例
Jun 28 Javascript
js 控制页面跳转的5种方法
Sep 09 Javascript
对Jquery中的ajax再封装,简化操作示例
Feb 12 Javascript
jquery弹窗插件colorbox绑定动态生成元素的方法
Jun 20 Javascript
JavaScript实现单击下拉框选择直接跳转页面的方法
Jul 02 Javascript
jQuery prototype冲突的2种解决方法(附demo示例下载)
Jan 21 Javascript
jquery实现文字单行横移或翻转(上下、左右跳转)
Jan 08 Javascript
js实现类bootstrap模态框动画
Feb 07 Javascript
从parcel.js打包出错到选择nvm的全部过程
Jan 23 Javascript
js加减乘除精确运算方法实例代码
Jan 17 #Javascript
Angular处理未可知异常错误的方法详解
Jan 17 #Javascript
react-native 实现购物车滑动删除效果的示例代码
Jan 15 #Javascript
vue element el-transfer增加拖拽功能
Jan 15 #Vue.js
关于uniApp editor微信滑动问题
Jan 15 #Javascript
关于javascript中的promise的用法和注意事项(推荐)
Jan 15 #Javascript
详解node.js创建一个web服务器(Server)的详细步骤
Jan 15 #Javascript
You might like
十天学会php之第九天
2006/10/09 PHP
php算开始时间到过期时间的相隔的天数
2011/01/12 PHP
PHP递归算法的详细示例分析
2013/02/19 PHP
php 升级到 5.3+ 后出现的一些错误,如 ereg(); ereg_replace(); 函数报错
2015/12/07 PHP
php的闭包(Closure)匿名函数初探
2016/02/14 PHP
利用javascript的面向对象的特性实现限制试用期
2011/08/04 Javascript
关于eval 与new Function 到底该选哪个?
2013/04/17 Javascript
javascript简单事件处理和with用法介绍
2013/09/16 Javascript
jquery $.fn $.fx是什么意思有什么用
2013/11/04 Javascript
浅谈JSON和JSONP区别及jQuery的ajax jsonp的使用
2014/11/23 Javascript
JavaScript更改原始对象valueOf的方法
2015/03/19 Javascript
D3.js实现柱状图的方法详解
2016/09/21 Javascript
JavaScript制作颜色反转小游戏
2016/09/25 Javascript
Vue.js创建Calendar日历效果
2016/11/03 Javascript
微信小程序  TLS 版本必须大于等于1.2问题解决
2017/02/22 Javascript
js+html5生成自动排列对话框实例
2017/10/09 Javascript
vue双向绑定及观察者模式详解
2019/03/19 Javascript
vue+web端仿微信网页版聊天室功能
2019/04/30 Javascript
在layui tab控件中载入外部html页面的方法
2019/09/04 Javascript
jQuery事件模型默认行为执行顺序及trigger()与 triggerHandler()比较实例分析
2020/04/30 jQuery
js校验开始时间和结束时间
2020/05/26 Javascript
element跨分页操作选择详解
2020/06/29 Javascript
对pandas中apply函数的用法详解
2018/04/10 Python
如何用Python制作微信好友个性签名词云图
2019/06/28 Python
python如何读取bin文件并下发串口
2019/07/05 Python
如何将 awk 脚本移植到 Python
2019/12/09 Python
浅谈python累加求和+奇偶数求和_break_continue
2020/02/25 Python
python爬虫scrapy图书分类实例讲解
2020/11/23 Python
Canvas高级路径操作之拖拽对象的实现
2019/08/05 HTML / CSS
全球领先的鞋类零售商:The Walking Company
2016/07/21 全球购物
见习期自我鉴定
2014/01/31 职场文书
国际贸易专业个人鉴定
2014/02/22 职场文书
环境日宣传活动总结
2014/07/09 职场文书
解放思想大讨论活动总结
2015/05/09 职场文书
小王子读书笔记
2015/06/29 职场文书
Python 如何安装Selenium
2021/05/06 Python