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 相关文章推荐
Firebug 字幕文件JSON地址获取代码
Oct 28 Javascript
JavaScript Event学习第六章 事件的访问
Feb 07 Javascript
node.js中的forEach()是同步还是异步呢
Jan 29 Javascript
不得不分享的JavaScript常用方法函数集(上)
Dec 23 Javascript
Mac下使用charles遇到的问题以及解决办法
Jan 10 Javascript
JS实现根据密码长度显示安全条功能
Mar 08 Javascript
vue计算属性时v-for处理数组时遇到的一个bug问题
Jan 21 Javascript
详解Vue.js自定义tipOnce指令用法实例
Dec 19 Javascript
Angular使用Restful的增删改
Dec 28 Javascript
Vue2.0使用嵌套路由实现页面内容切换/公用一级菜单控制页面内容切换(推荐)
May 08 Javascript
vue.js中ref及$refs的使用方法解析
Oct 08 Javascript
vue使用vant中的checkbox实现全选功能
Nov 17 Vue.js
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
Windows下安装Memcached的步骤说明
2010/04/25 PHP
php自定义的格式化时间示例代码
2013/12/05 PHP
PHP PDO fetch 模式各种参数的输出结果一览
2015/01/07 PHP
7个鲜为人知却非常实用的PHP函数
2015/07/01 PHP
PHP实现模拟http请求的方法分析
2017/12/20 PHP
tp5 实现列表数据根据状态排序
2019/10/18 PHP
JS 实现点击a标签的时候让其背景更换
2013/10/15 Javascript
浅谈javascript六种数据类型以及特殊注意点
2013/12/20 Javascript
javascript实现十秒钟后注册按钮可点击的方法
2015/05/13 Javascript
理解JavaScript中worker事件api
2015/12/25 Javascript
浅谈js之字面量、对象字面量的访问、关键字in的用法
2016/11/20 Javascript
AngularJS指令与指令之间的交互功能示例
2016/12/14 Javascript
关于webpack2和模块打包的新手指南(小结)
2017/08/07 Javascript
Nginx设置为Node.js的前端服务器方法总结
2019/03/27 Javascript
过滤器vue.filters的使用方法实现
2019/09/18 Javascript
ElementUI多个子组件表单的校验管理实现
2019/11/07 Javascript
react ant Design手动设置表单的值操作
2020/10/31 Javascript
[01:51]历届DOTA2国际邀请赛举办地回顾 TI9落地上海
2018/08/26 DOTA
Python爬虫基础之XPath语法与lxml库的用法详解
2018/09/13 Python
Python实现EM算法实例代码
2020/10/04 Python
html5 canvas里绘制椭圆并保持线条粗细均匀的技巧
2013/03/25 HTML / CSS
Tiqets英国:智能手机上的文化和娱乐门票
2019/07/10 全球购物
衰败城市英国官网:Urban Decay英国
2020/04/29 全球购物
如何查询Oracle数据库中已经创建的索引
2013/10/11 面试题
毕业生就业自荐信
2013/12/04 职场文书
银行实习鉴定
2013/12/13 职场文书
建筑文秘专业个人求职信范文
2013/12/28 职场文书
办公室秘书自我鉴定
2014/01/18 职场文书
药品促销活动方案
2014/02/14 职场文书
吨的认识教学反思
2014/04/27 职场文书
五水共治捐款倡议书
2014/05/14 职场文书
学生会竞选演讲稿怎么写
2014/08/26 职场文书
2015小学五年级班主任工作总结
2015/05/21 职场文书
《天使的翅膀》读后感3篇
2019/12/20 职场文书
Canvas跟随鼠标炫彩小球的实现
2021/04/11 Javascript