JavaScript 任务池

前端APP 投稿 72700 1 评论

JavaScript 任务池

线程池

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

  1. 节省操作系统资源

  2. 限制最大线程数。

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

分析

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 {}
}

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

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

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

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

那么我们可以这么操作:

  1. 定义一个变量用来记录当前的空闲任务数量;

  2. 执行  时,会选取当前任务数量和空闲任务数二者相比较小的一个;

  3. 根据该值进行循环,每次循环弹出  第一位的任务进行执行;

  4. 执行前将空闲任务数 -1,执行完毕后空闲任务数 +1,并再次执行 。

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(;
    };
  }
}

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

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

可以利用防抖 +  的特性来完成。

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 任务池

赞同 (196) or 分享 (0)
游客 发表我的评论   换个身份
取消评论

表情
(1)个小伙伴在吐槽
  1. 非常震撼。
    幽暗刺杀者 2023-09-04 00:30 (2年前) 回复