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 相关文章推荐
js判断ie版本号的简单实现代码
Mar 05 Javascript
jQuery 如何先创建、再修改、后添加DOM元素
May 20 Javascript
jquery根据属性和index来查找属性值并操作
Jul 25 Javascript
javascript三元运算符用法实例
Apr 16 Javascript
利用Node.js制作爬取大众点评的爬虫
Sep 22 Javascript
使用jquery判断一个元素是否含有一个指定的类(class)实例
Feb 12 Javascript
详解使用fetch发送post请求时的参数处理
Apr 05 Javascript
JS验证全角与半角及相互转化的介绍
May 18 Javascript
原生JS实现的放大镜特效示例【测试可用】
Dec 08 Javascript
JavaScript面试技巧之数组的一些不low操作
Mar 22 Javascript
vue键盘事件点击事件加native操作
Jul 27 Javascript
JS前端基于canvas给图片添加水印
Nov 11 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
模拟xcopy的函数
2006/10/09 PHP
自动分页的不完整解决方案
2007/01/12 PHP
YII路径的用法总结
2014/07/09 PHP
推荐一款PHP+jQuery制作的列表分页的功能模块
2014/10/14 PHP
PHP读取汉字的点阵数据
2015/06/22 PHP
jQuery 版本的文本输入框检查器Input Check
2009/07/09 Javascript
JavaScript 替换Html标签实现代码
2009/10/14 Javascript
使用IE6看老赵的博客 jQuery初探
2010/01/17 Javascript
jQuery Deferred和Promise创建响应式应用程序详细介绍
2013/03/05 Javascript
a标签的href和onclick 的事件的区别介绍
2013/07/26 Javascript
JS实现的倒计时效果实例(2则实例)
2015/12/23 Javascript
JS+CSS实现的漂亮渐变背景特效代码(6个渐变效果)
2016/03/25 Javascript
jQuery将表单序列化成一个Object对象的实例
2016/11/29 Javascript
基于Nodejs利用socket.io实现多人聊天室
2017/02/22 NodeJs
详解node单线程实现高并发原理与node异步I/O
2017/09/21 Javascript
动态Axios的配置步骤详解
2018/01/12 Javascript
js实现上传图片并显示图片名称
2019/12/18 Javascript
JavaScript数组常用的增删改查与其他属性详解
2020/10/13 Javascript
使用Python下载Bing图片(代码)
2013/11/07 Python
Python Web框架Flask中使用新浪SAE云存储实例
2015/02/08 Python
python多进程共享变量
2016/04/06 Python
100行Python代码实现自动抢火车票(附源码)
2018/01/11 Python
python读取文本中的坐标方法
2018/10/14 Python
PyCharm第一次安装及使用教程
2020/01/08 Python
python 爬取疫情数据的源码
2020/02/09 Python
Python实现自动访问网页的例子
2020/02/21 Python
使用keras框架cnn+ctc_loss识别不定长字符图片操作
2020/06/29 Python
详解PyQt5中textBrowser显示print语句输出的简单方法
2020/08/07 Python
HTML5之SVG 2D入门11—用户交互性(动画)介绍及应用
2013/01/30 HTML / CSS
Linux文件操作命令都有哪些
2015/02/27 面试题
转预备党员政审材料
2014/02/06 职场文书
《逃家小兔》教学反思
2014/02/23 职场文书
课程设计的心得体会
2014/09/03 职场文书
交通事故协议书范文
2014/10/23 职场文书
交通事故一次性赔偿协议书范本
2014/11/02 职场文书
少先大队干部竞选稿
2015/11/20 职场文书