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 相关文章推荐
JavaScript Archive Network 集合
May 12 Javascript
Jquery Validate 正则表达式实用验证代码大全
Aug 23 Javascript
js 限制input只能输入数字、字母和汉字等等
Dec 18 Javascript
全屏滚动插件fullPage.js使用实例解析
Oct 21 Javascript
bootstrap网格系统使用方法解析
Jan 13 Javascript
JavaScript实现的select点菜功能示例
Jan 16 Javascript
基于VUE选择上传图片并页面显示(图片可删除)
May 25 Javascript
js实现图片旋转 js滚动鼠标中间对图片放大缩小
Jul 05 Javascript
Angular 作用域scope的具体使用
Dec 11 Javascript
layer弹出层自定义提交取消按钮的例子
Sep 10 Javascript
Vue基本指令实例图文讲解
Feb 25 Vue.js
详解js创建对象的几种方式和对象方法
Mar 01 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
PHP5.2下chunk_split()函数整数溢出漏洞 分析
2007/06/06 PHP
php实现的CSS更新类实例
2014/09/22 PHP
PHP也能干大事之PHP中的编码解码详解
2015/04/20 PHP
php计算整个目录大小的方法
2015/06/19 PHP
php组合排序简单实现方法
2016/10/15 PHP
PHP常用函数之格式化时间操作示例
2019/10/21 PHP
PHP filter_var() 函数, 验证判断EMAIL,URL等
2021/03/09 PHP
在网站上应该用的30个jQuery插件整理
2011/11/03 Javascript
jQuery Ajax提交表单查询获得数据实例代码
2012/09/19 Javascript
JavaScript为事件句柄绑定监听函数实例详解
2015/12/15 Javascript
Javascript点击其他任意地方隐藏关闭DIV实例
2016/06/21 Javascript
Vue.js仿微信聊天窗口展示组件功能
2017/08/11 Javascript
webpack处理 css\less\sass 样式的方法
2017/08/21 Javascript
Vue.js项目模板搭建图文教程
2017/09/20 Javascript
Vue动态路由缓存不相互影响的解决办法
2019/02/19 Javascript
Python enumerate索引迭代代码解析
2018/01/19 Python
Python实现读取Properties配置文件的方法
2018/03/29 Python
Python中的单行、多行、中文注释方法
2018/07/19 Python
Flask框架踩坑之ajax跨域请求实现
2019/02/22 Python
Python实现Linux监控的方法
2019/05/16 Python
对python 中class与变量的使用方法详解
2019/06/26 Python
pandas读取csv文件提示不存在的解决方法及原因分析
2020/04/21 Python
python 读取yaml文件的两种方法(在unittest中使用)
2020/12/01 Python
关于css中margin的值和垂直外边距重叠问题
2020/10/27 HTML / CSS
HTML5 video视频字幕的使用和制作方法
2018/05/03 HTML / CSS
美国领先的医疗警报服务:Philips Lifeline
2018/03/12 全球购物
西海岸男士和男童服装:Johnnie-O
2018/03/15 全球购物
什么是会话Bean
2015/05/14 面试题
UNIX文件系统分类
2014/11/11 面试题
js正则匹配markdown里的图片标签的实现
2021/03/24 Javascript
工业设计专业个人求职信范文
2013/12/28 职场文书
服装采购员岗位职责
2014/03/15 职场文书
质量承诺书怎么写
2014/05/24 职场文书
2015年销售内勤工作总结
2015/04/27 职场文书
校车司机安全责任书
2015/05/11 职场文书
CSS中float高度塌陷问题的四种解决方案
2022/04/18 HTML / CSS