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 中的事件教程
Apr 05 Javascript
JavaScript 数组循环引起的思考
Jan 01 Javascript
基于jquery的无限级联下拉框js插件
Oct 29 Javascript
JS代码同步文本框内容的实例方法
Jul 12 Javascript
javascript操作excel生成报表示例
May 08 Javascript
详解jquery事件delegate()的使用方法
Jan 25 Javascript
Angularjs 创建可复用组件实例代码
Oct 09 Javascript
Node接收电子邮件的实例代码
Jul 21 Javascript
详解react-redux插件入门
Apr 19 Javascript
vue中使用vue-cli接入融云实现即时通信
Apr 19 Javascript
Vue CLI3移动端适配(px2rem或postcss-plugin-px2rem)
Apr 27 Javascript
JavaScript实现消消乐的源代码
Jan 12 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
天使彦史上最神还原,性别曝光的那一刻,百万网友恋爱了
2020/03/02 国漫
PHP 最大运行时间 max_execution_time修改方法
2010/03/08 PHP
PHP获取文件相对路径的方法
2015/02/26 PHP
PHP多进程编程实例详解
2017/07/19 PHP
PHP常用header头定义代码示例汇总
2020/08/29 PHP
在js中使用&quot;with&quot;语句中跨frame的变量引用问题
2007/03/08 Javascript
我遇到的参数传递中 双引号单引号嵌套问题
2010/02/11 Javascript
JavaScript 输入框内容格式验证代码
2010/02/11 Javascript
dtree 网页树状菜单及传递对象集合到js内,动态生成节点
2012/04/14 Javascript
JavaScript更改原始对象valueOf的方法
2015/03/19 Javascript
JS模式之单例模式基本用法
2015/06/30 Javascript
常用的javascript设计模式
2017/01/11 Javascript
Bootstrap datepicker日期选择器插件使用详解
2017/07/26 Javascript
html中通过JS获取JSON数据并加载的方法
2017/11/30 Javascript
VueJs监听window.resize方法示例
2018/01/17 Javascript
vue项目webpack中Npm传递参数配置不同域名接口
2018/06/15 Javascript
AJAX在JQuery中的应用详解
2019/01/30 jQuery
如何使用JavaScript实现栈与队列
2019/06/24 Javascript
django js 实现表格动态标序号的实例代码
2019/07/12 Javascript
微信小程序实现上拉加载功能
2019/11/20 Javascript
Vue filter 过滤器、以及在table中的使用介绍
2020/09/07 Javascript
基于Python如何使用AIML搭建聊天机器人
2016/01/27 Python
python字典的setdefault的巧妙用法
2019/08/07 Python
python 实现的车牌识别项目
2021/01/25 Python
巴西最大的在线约会网站:ParPerfeito
2018/07/11 全球购物
大学应届生求职简历的自我评价
2013/10/08 职场文书
迟到检讨书300字
2014/02/14 职场文书
教师年度考核评语
2014/04/28 职场文书
2014年小学教师工作自我评价
2014/09/22 职场文书
2014年业务工作总结
2014/11/17 职场文书
2014年挂职干部工作总结
2014/12/06 职场文书
2014会计年终工作总结
2014/12/20 职场文书
赵氏孤儿观后感
2015/06/09 职场文书
2019暑假学生安全口号
2019/06/27 职场文书
Python中rapidjson参数校验实现
2021/07/25 Python
动画「半妖的夜叉姬」新BD特典图公开
2022/03/22 日漫