【前端学习笔记_JS】设计任务队列,控制请求最大并发数

2,069 阅读3分钟

JavaScript设计任务队列,控制请求最大并发数。

场景:前端页面中需要同时发送20个请求,但是服务端有限制,需要前端控制并发数,保证最多同时发送10个请求。

要求:

  1. 最多同时执行的任务数为10个。
  2. 当前任务执行完成后,释放队列空间,自动执行下一个任务。
  3. 所有任务添加到任务队列后,自动开始执行任务。
知识点:Event Loop/宏任务/微任务

事件队列:

JS引擎遇到一个异步事件后并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当一个异步事件返回结果后,JS会将这个事件加入与当前执行栈不同的另一个队列,我们称之为事件队列。被放入事件队列不会立刻执行其回调,而是等待当前执行栈中的所有任务都执行完毕, 主线程处于闲置状态时,主线程会去查找事件队列是否有任务。如果有,那么主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其中的同步代码...,如此反复,这样就形成了一个无限的循环。这就是这个过程被称为“事件循环(Event Loop)”的原因。

宏任务与微任务

不同的异步任务被分为两类:微任务(micro task)和宏任务(macro task)。

以下事件属于宏任务:

  • setInterval()
  • setTimeout()

以下事件属于微任务

  • Promise().resolve().then(new Promise中传入的函数是立刻执行的)
  • new MutaionObserver()

当当前执行栈执行完毕时, 每一次 loop 的时候会并且先执行"最前面"的宏任务, 然后执行当前 loop 下所有的微任务, 所有微任务完毕之后, 进入下一次 loop, 执行接下来的宏任务, 重复上述过程

完整代码

 function createTask(i) {
     return () => {
         return new Promise((resolve, reject) => {
             setTimeout(() => {
                 resolve(i);
             }, 2000);
         });
     };
 }
 ​
 class TaskQueue {
     constructor() {
         this.max = 10; //最大并发数
         this.taskList = [] //用shift方法实现先进先出
         setTimeout(() => { //这里初始化队列后自动执行,后续有新任务添加则需要手动执行。
             this.run()
         })
     }
     addTask(task) {
         this.taskList.push(task);
     }
 ​
     run() {
         const length = this.taskList.length;
 ​
         if (!length) {
             return;
         }
         const min = Math.min(length, this.max);// 控制并发数量
         for (let i = 0; i < min; i++) {
             this.max--; //开始占用一个任务的空间
             const task = this.taskList.shift();
             task().then(res => {
                 console.log(res);
             }).catch(error => {
                 console.log(error);
             }).finally(() => {
                 this.max++; //任务完成,释放空间
                 this.run();//自动进行下一个任务
             })
         }
     }
 }
 ​
 const taskQueue = new TaskQueue();
 for (let i = 0; i < 20; i++) {
     const task = createTask(i);
     taskQueue.addTask(task);//当所有任务添加到队列中后自动执行。
 }

由于addTask是同步方法,在测试中,当所有addTask执行完毕后,会执行构造器中的setTimeout方法,也即自动执行run

此时第一次执行run方法时,会将所有的run方法串联起来。此时for循环取任务的时候也是在当前Event loop中的同步任务,因此会执行完循环,也就是填充10个任务(模拟网速较快),当挂起的异步任务有完成,finally执行,释放任务空间,此时继续执行run方法填充任务,以此循环。

推荐阅读/参考资料

[Promise控制请求并发数​]  www.bilibili.com/video/BV1XZ… JS每日一题:如何设计一个任务队列?控制请求最大并发数

[​Event Loop详解]  zhuanlan.zhihu.com/p/33058983 

[JS深入系列​]  github.com/mqyqingfeng…