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 相关文章推荐
DIV菜单层实现代码
Nov 19 Javascript
代码触发js事件(click、change)示例应用
Dec 13 Javascript
jquery 3D 标签云示例代码
Jun 12 Javascript
js中回调函数的学习笔记
Jul 31 Javascript
jQuery实现仿腾讯视频列表分页效果的方法
Aug 07 Javascript
探析浏览器执行JavaScript脚本加载与代码执行顺序
Jan 12 Javascript
javascript操作cookie
Jan 17 Javascript
Bootstrap提示框效果的实例代码
Jul 12 Javascript
详解Vue-axios 设置请求头问题
Dec 06 Javascript
three.js实现圆柱体
Dec 30 Javascript
微信小程序的线程架构【推荐】
May 14 Javascript
vue el-upload上传文件的示例代码
Dec 21 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代码
2010/08/08 PHP
PHP制作百度词典查词采集器
2015/01/29 PHP
PHP微信开发用Cache 解决数据缓存
2016/07/11 PHP
PHP 返回数组后处理方法(开户成功后弹窗提示)
2017/07/03 PHP
关于ThinkPhp 框架表单验证及ajax验证问题
2017/07/19 PHP
ThinkPHP类似AOP思想的参数验证的实现方法
2019/12/18 PHP
javascript获取鼠标位置部分的实例代码(兼容IE,FF)
2013/08/05 Javascript
javascript自启动函数的问题探讨
2013/10/05 Javascript
解析jQueryEasyUI的使用
2016/11/22 Javascript
jquery实现弹窗功能(窗口居中显示)
2017/02/27 Javascript
react native实现往服务器上传网络图片的实例
2017/08/07 Javascript
详解tween.js的使用教程
2017/09/14 Javascript
Vue与Node.js通过socket.io通信的示例代码
2018/07/25 Javascript
webpack 代码分离优化快速指北
2019/05/18 Javascript
解决layui-table单元格设置为百分比在ie8下不能自适应的问题
2019/09/28 Javascript
JS变量提升原理与用法实例浅析
2020/05/22 Javascript
python复制与引用用法分析
2015/04/08 Python
Python 3中的yield from语法详解
2017/01/18 Python
django定期执行任务(实例讲解)
2017/11/03 Python
浅谈Django REST Framework限速
2017/12/12 Python
Python实现定制自动化业务流量报表周报功能【XlsxWriter模块】
2019/03/11 Python
Django之使用内置函数和celery发邮件的方法示例
2019/09/16 Python
Python input函数使用实例解析
2019/11/22 Python
tensorflow实现对张量数据的切片操作方式
2020/01/19 Python
python GUI库图形界面开发之PyQt5开发环境配置与基础使用
2020/02/25 Python
python dict乱码如何解决
2020/06/07 Python
python中scipy.stats产生随机数实例讲解
2021/02/19 Python
Eastbay官网:美国最大的运动鞋网络零售商
2016/07/27 全球购物
英国网上花店:Bunches
2016/11/29 全球购物
怎样写好自我鉴定
2013/12/04 职场文书
小学新学期教师寄语
2014/01/18 职场文书
《棉鞋里的阳光》教学反思
2014/04/24 职场文书
2015年实习班主任工作总结
2015/04/23 职场文书
话题作文之学会尊重
2019/12/16 职场文书
Python基础知识之变量的详解
2021/04/14 Python
Java网络编程之UDP实现原理解析
2021/09/04 Java/Android