JavaScript 并发控制实现原理:async-pool 源码解析与 Promise 手写

作者:函数茶馆时间:2026-10-24 11:22:29浏览:617

本文深入解析 JavaScript 中异步任务并发控制的实现机制,以 async-pool 库为例,详细剖析 ES7 与 ES6 两种版本的源码逻辑。文章重点讲解了如何利用 Promise.all 和 Promise.race 限制同时执行的异步任务数量,并附带手写简易版 Promise.all 和 Promise.race 的代码实现,帮助开发者理解底层原理。

一、并发控制简介

假设存在 6 个待处理的任务,我们需要限制同一时刻执行的任务数量,例如最多允许 2 个任务并行运行。每当“正在执行任务列表”中有一个任务完成,程序便会自动从“待办任务列表”中取出新任务并加入执行队列。为了直观展示这一过程,以下通过三张示意图进行说明:

1.1 阶段一

一、并发控制简介

1.2 阶段二

一、并发控制简介

1.3 阶段三

一、并发控制简介

了解并发控制的基本概念后,我们将以 GitHub 上的 async-pool 库为例,深入探讨异步任务并发控制的具体代码实现。

https://github.com/rxaviers/async-pool

Run multiple promise-returning & async functions with limited concurrency using native ES6/ES7。

二、并发控制的实现

async-pool 库提供了基于 ES7 和 ES6 的两种实现版本。在深入分析源码之前,我们先通过示例了解其基本用法。

2.1 asyncPool 的使用


const timeout = i => new Promise(resolve => setTimeout(() => resolve(i), i));
await asyncPool(2, [1000, 5000, 3000, 2000], timeout);

在上述代码中,我们调用了 async-pool 库提供的 asyncPool 函数来实现异步任务的并发限制。该函数的签名定义如下:


function asyncPool(poolLimit, array, iteratorFn){ ... }

该函数接收 3 个参数:

  • poolLimit(数字类型):表示限制的并发数;
  • array(数组类型):表示任务数组;
  • iteratorFn(函数类型):表示迭代函数,用于处理每个任务项,该函数需返回一个 Promise 对象或异步函数。

针对上述示例,调用 asyncPool 函数后的执行流程如下所示:


const timeout = i => new Promise(resolve => setTimeout(() => resolve(i), i));
await asyncPool(2, [1000, 5000, 3000, 2000], timeout);
// Call iterator (i = 1000)
// Call iterator (i = 5000)
// Pool limit of 2 reached, wait for the quicker one to complete...
// 1000 finishes
// Call iterator (i = 3000)
// Pool limit of 2 reached, wait for the quicker one to complete...
// 3000 finishes
// Call iterator (i = 2000)
// Itaration is complete, wait until running ones complete...
// 5000 finishes
// 2000 finishes
// Resolves, results are passed in given array order `[1000, 5000, 3000, 2000]`.

通过观察上述注释信息,我们可以大致理清 asyncPool 函数内部的调度逻辑。接下来,我们将首先分析 asyncPool 函数的 ES7 版本实现。

2.2 asyncPool ES7 实现


async function asyncPool(poolLimit, array, iteratorFn) {
  const ret = []; // 存储所有的异步任务
  const executing = []; // 存储正在执行的异步任务
  for (const item of array) {
    // 调用iteratorFn函数创建异步任务
    const p = Promise.resolve().then(() => iteratorFn(item, array));
    ret.push(p); // 保存新的异步任务

    // 当poolLimit值小于或等于总任务个数时,进行并发控制
    if (poolLimit <= array.length) {
      // 当任务完成后,从正在执行的任务数组中移除已完成的任务
      const e = p.then(() => executing.splice(executing.indexOf(e), 1));
      executing.push(e); // 保存正在执行的异步任务
      if (executing.length >= poolLimit) {
        await Promise.race(executing); // 等待较快的任务执行完成
      }
    }
  }
  return Promise.all(ret);
}

在 ES7 版本的代码中,充分利用了 Promise.all 和 Promise.race 的特性,并结合 ES7 提供的 async/await 语法,最终实现了并发控制。通过语句 await Promise.race(executing);,程序会等待“正在执行任务列表”中较快完成的任务结束后,再继续执行下一次循环。

asyncPool 的 ES7 实现相对简洁,接下来我们探讨在不使用 async/await 特性的情况下,如何实现相同的功能。

2.3 asyncPool ES6 实现


function asyncPool(poolLimit, array, iteratorFn) {
  let i = 0;
  const ret = []; // 存储所有的异步任务
  const executing = []; // 存储正在执行的异步任务
  const enqueue = function () {
    if (i === array.length) {
      return Promise.resolve();
    }
    const item = array[i++]; // 获取新的任务项
    const p = Promise.resolve().then(() => iteratorFn(item, array));
    ret.push(p);

    let r = Promise.resolve();

    // 当poolLimit值小于或等于总任务个数时,进行并发控制
    if (poolLimit <= array.length) {
      // 当任务完成后,从正在执行的任务数组中移除已完成的任务
      const e = p.then(() => executing.splice(executing.indexOf(e), 1));
      executing.push(e);
      if (executing.length >= poolLimit) {
        r = Promise.race(executing); 
      }
    }
 
    // 正在执行任务列表 中较快的任务执行完成之后,才会从array数组中获取新的待办任务
    return r.then(() => enqueue());
  };
  return enqueue().then(() => Promise.all(ret));
}

在 ES6 实现版本中,核心控制逻辑通过内部封装的 enqueue 函数来实现。只有当 Promise.race(executing) 返回的 Promise 对象状态变为已完成时,才会调用 enqueue 函数,从 array 数组中获取新的待处理任务。

三、阿宝哥有话说

在 asyncPool 库的 ES7 和 ES6 具体实现中,均使用了 Promise.all 和 Promise.race 函数。其中,手写 Promise.all 是一道常见的面试题。借此机会,我们将一起手写简易版的 Promise.all 和 Promise.race 函数。

3.1 手写 Promise.all

Promise.all(iterable) 方法会返回一个 promise 对象。当输入的所有 promise 对象状态都变为 resolved 时,返回的 promise 对象将以数组形式返回每个 promise 对象 resolve 后的结果。若输入的任何 promise 对象状态变为 rejected,则返回的 promise 对象会 reject 对应的错误信息。


Promise.all = function (iterators) {
  return new Promise((resolve, reject) => {
    if (!iterators || iterators.length === 0) {
      resolve([]);
    } else {
      let count = 0; // 计数器,用于判断所有任务是否执行完成
      let result = []; // 结果数组
      for (let i = 0; i < iterators.length; i++) {
        // 考虑到iterators[i]可能是普通对象,则统一包装为Promise对象
        Promise.resolve(iterators[i]).then(
          (data) => {
            result[i] = data; // 按顺序保存对应的结果
            // 当所有任务都执行完成后,再统一返回结果
            if (++count === iterators.length) {
              resolve(result);
            }
          },
          (err) => {
            reject(err); // 任何一个Promise对象执行失败,则调用reject()方法
            return;
          }
        );
      }
    }
  });
};

需要注意的是,对于 Promise.all 的标准实现而言,其参数是一个可迭代对象,例如 Array、String 或 Set 等。

3.2 手写 Promise.race

Promise.race(iterable) 方法会返回一个 promise 对象。一旦迭代器中的某个 promise 对象 resolved 或 rejected,返回的 promise 对象就会 resolve 或 reject 相应的值。


Promise.race = function (iterators) {
  return new Promise((resolve, reject) => {
    for (const iter of iterators) {
      Promise.resolve(iter)
        .then((res) => {
          resolve(res);
        })
        .catch((e) => {
          reject(e);
        });
    }
  });
};

本文详细分析了 async-pool 异步任务并发控制的具体实现,并为了帮助读者更好地理解 async-pool 的核心代码,最后带领大家手写简易版的 Promise.all 和 Promise.race 函数。除了 Promise.all 函数外,还存在另一个相关函数 —— Promise.allSettled,该函数用于解决 Promise.all 存在的问题,感兴趣的小伙伴可以自行研究一下。

四、参考资源

Github - async-pool MDN - Promise.all MDN - Promise.race MDN - Promise.allSettled

以上就是JavaScript 中如何实现并发控制的详细内容,更多关于JavaScript实现并发控制的资料请关注本站其它相关文章!

作者最新文章