JavaScript 任务池

本文写于 2022 年 5 月 13 日

线程池

在多线程语言中,我们通常不会随意的在需要启动线程的时候去启动,而是会选择创建一个线程池。

所谓线程池,本意其实就是(不止这些作用,其余作用可以自行查阅):

  1. 节省操作系统资源
  2. 限制最大线程数。

对于 JavaScript 来说,虽然不存在“启动线程”这种问题,但我们还是可以通过类似的思想,来限制我们做异步操作的数量

分析

首先我们需要一个数组,用它来存储尚未执行的任务,每个任务都是一个函数,这个函数必须要返回一个 Promise。

type Task = () => Promise<unknown>;

const tasks: Task[] = [];

其次我们需要一个方法来进行任务的添加。

function addTask(task: Task): void;

最后我们需要一个函数来执行我们所有的 task。

而在这之前,我们还需要定义一个值,来定义同时执行的异步任务的最大数量。

function execTasks(): void;

实现

根据我们的分析,我们可以写下基础的代码如下:

interface TaskPool {
addTask(task: Task): void;
} type Task = () => Promise<unknown>; function newTaskPool(max = 10): TaskPool {
const tasks: Task[] = []; function addTask(task: Task): void {} function execTasks(): void {}
}

新增任务非常简单,我们写出如下代码填充 addTask

function addTask(task: Task): void {
tasks.push(task);
}

接下来就是重头戏。如何实现 execTasks 方法来限制最大异步任务数量呢?

首先我们来明确一点,在下面这个场景中,如果 foo 函数是异步操作,那么是不会阻塞我们的代码执行的。

console.log("Before");
foo();
console.log("After");

那么我们可以这么操作:

  1. 定义一个变量用来记录当前的空闲任务数量;
  2. 执行 execTasks 时,会选取当前任务数量和空闲任务数二者相比较小的一个;
  3. 根据该值进行循环,每次循环弹出 tasks 第一位的任务进行执行;
  4. 执行前将空闲任务数 -1,执行完毕后空闲任务数 +1,并再次执行 execTasks
let leisure = max;

function execTasks(): void {
if (tasks.length === 0) return; const execTaskNum = Math.min(tasks.length, leisure);
for (let i = 0; i < execTaskNum; i++) {
const task = tasks.shift();
if (!task) continue; leisure--;
task().finally(() => {
leisure++;
execTasks();
});
}
}

最后我们只剩下了一个问题了,我们如何在 addTask 后执行 execTasks,但又不会让下面这种情况导致频繁执行 execTasks

for (let i = 0; i < 100; i++) addTask();

可以利用防抖 + setTimeout(() => {},0) 的特性来完成。

function addTask(task: Task) {
tasks.push(task);
execTasksAfterAdd();
} // 这里借用了 lodash 的 debounce 函数,具体实现不多说,可以看我以前的文章:防抖与节流
const execTasksAfterAdd = debounce(execTasks);

完整代码:

import { debounce } from "lodash";

interface TaskQueue {
addTask: (task: () => Promise<any>) => void;
} function newTaskQueue(maxTaskNum = 10): TaskQueue {
let _leisure = maxTaskNum; const _tasks: Array<() => Promise<any>> = []; function addTask(task: () => Promise<any>) {
_tasks.push(task);
execAfterTask();
} const execAfterTask = debounce(execTasks); function execTasks() {
if (_tasks.length === 0) return; const execTaskNum = Math.min(_tasks.length, _leisure);
for (let i = 0; i < execTaskNum; i++) {
const task = _tasks.shift();
if (!task) continue; _leisure--;
task().finally(() => {
_leisure++;
execTasks();
});
}
} return { addTask };
} const queue = newTaskQueue(5); for (let i = 0; i < 10; i++) {
queue.addTask(function () {
return new Promise<void>((resolve) => {
setTimeout(() => resolve(), 800);
});
});
}

使用场景

其实这种做法的使用场景是比较少的。

绝大多数情况我们都不需要这么去做,除非碰到很极端的需求。

例如我们需要用 Node.js 去设计一个吞吐量极大的服务,那么同时发生大量的网络请求很可能把带宽直接打满,导致后续的请求无法打到该服务,此时就可以使用任务池来控制最大网络请求量。

(完)

JavaScript 任务池的更多相关文章

  1. javascript中的队列结构

    1.概念 队列和栈结构不同,栈是一种后进先出的结构,而队列是一种先进先出的结构.队列也是一种表结构,不同的是队列只能在队尾插入元素,在队首删除元素,可以将队列想象成一个在超时等待排队付钱的队伍,或者在 ...

  2. 第五章:javascript:队列

    队列是一种列表,不同的是队列只能在末尾插入元素,在队首删除元素.队列用于存储按顺序排列的数据.先进先出.这点和栈不一样,在栈中,最后入栈的元素反被优先处理.可以将队列想象成银行排队办理业务的人,排队在 ...

  3. 数据结构与算法JavaScript描述——队列

    注:澄清一个bug: /** * 删除队首的元素: */ function dequeue(){ return this.dataStore.shift(); } 应该有return:   队列是一种 ...

  4. JavaScript之父Brendan Eich,Clojure 创建者Rich Hickey,Python创建者Van Rossum等编程大牛对程序员的职业建议

    软件开发是现时很火的职业.据美国劳动局发布的一项统计数据显示,从2014年至2024年,美国就业市场对开发人员的需求量将增长17%,而这个增长率比起所有职业的平均需求量高出了7%.很多人年轻人会选择编 ...

  5. javascript中的Array对象 —— 数组的合并、转换、迭代、排序、堆栈

    Array 是javascript中经常用到的数据类型.javascript 的数组其他语言中数组的最大的区别是其每个数组项都可以保存任何类型的数据.本文主要讨论javascript中数组的声明.转换 ...

  6. Javascript 的执行环境(execution context)和作用域(scope)及垃圾回收

    执行环境有全局执行环境和函数执行环境之分,每次进入一个新执行环境,都会创建一个搜索变量和函数的作用域链.函数的局部环境不仅有权访问函数作用于中的变量,而且可以访问其外部环境,直到全局环境.全局执行环境 ...

  7. 探究javascript对象和数组的异同,及函数变量缓存技巧

    javascript中最经典也最受非议的一句话就是:javascript中一切皆是对象.这篇重点要提到的,就是任何jser都不陌生的Object和Array. 有段时间曾经很诧异,到底两种数据类型用来 ...

  8. 读书笔记:JavaScript DOM 编程艺术(第二版)

    读完还是能学到很多的基础知识,这里记录下,方便回顾与及时查阅. 内容也有自己的一些补充. JavaScript DOM 编程艺术(第二版) 1.JavaScript简史 JavaScript由Nets ...

  9. 《Web 前端面试指南》1、JavaScript 闭包深入浅出

    闭包是什么? 闭包是内部函数可以访问外部函数的变量.它可以访问三个作用域:首先可以访问自己的作用域(也就是定义在大括号内的变量),它也能访问外部函数的变量,和它能访问全局变量. 内部函数不仅可以访问外 ...

  10. JavaScript权威指南 - 函数

    函数本身就是一段JavaScript代码,定义一次但可能被调用任意次.如果函数挂载在一个对象上,作为对象的一个属性,通常这种函数被称作对象的方法.用于初始化一个新创建的对象的函数被称作构造函数. 相对 ...

随机推荐

  1. Spark入门实战系列--6.SparkSQL(下)--Spark实战应用

    [注]该系列文章以及使用到安装包/测试数据 可以在<倾情大奉送--Spark入门实战系列>获取 .运行环境说明 1.1 硬软件环境 线程,主频2.2G,10G内存 l  虚拟软件:VMwa ...

  2. 创建第一个ArcGIS API for Silverlight应用

    原文:创建第一个ArcGIS API for Silverlight应用 在完成前面的开发环境搭建以后,接下来实现我们的第一个ArcGIS API forSilverlight应用程序. 接下来我们一 ...

  3. nodejs for centos配置

    mongodb http://www.cnblogs.com/zhoulf/archive/2013/01/31/2887439.html nodejs http://zhaohe162.blog.1 ...

  4. veridata实验例(5)在更改主键列值,update操作将被分成两个语句

    veridata实验例(5)更改主键列值,update操作将被分成两个语句 续接"veridata实验举例(4)验证veridata查找出updata.delete操作导致的不同步现象&qu ...

  5. Summary on deep learning framework --- Torch7

    Summary on deep learning framework --- Torch7  2018-07-22 21:30:28 1. 尝试第一个 CNN 的 torch版本, 代码如下: -- ...

  6. bash 5

    1)bash支持一维数组(不支持多维数组),并且没有限定数组的大小. 类似于 C 语言,数组元素的下标由 0 开始编号.获取数组中的元素要 利用下标,下标可以是整数或算术表达式,其值应大于或等于 0. ...

  7. 小鬼难缠--python小bug备忘

    今天编译pyhon做人脸识别,遇到几个问题,做个记录吧. 编译报错: File "harrClassifier.py", line 17, in <module> fl ...

  8. mysql重复start stop slave测试

    如题,测试重复start slave, stop slave是否会有报错. 版本 5.7.21 重复start slave测试 第一次start >start slave; Query OK, ...

  9. 通过sftp操作Linux服务器上的文件(java)

    本文为实现对linux服务器文件的操作.windows服务器不支持. 引入jar包:jsch-0.1.42.jar package com.csvreader.sftp; import java.io ...

  10. MySQL连接查询(多表查询)

    基本含义 连接就是指两个或两个以上的表(数据源) “连接起来成为一个数据源”. 连接语法的基本形式:from 表1 [连接方式] join 表2 [on 连接条件]; 连接的结果可以当做一个“表”来使 ...