JavaScript 并发控制实现原理:async-pool 源码解析与 Promise 手写
本文深入解析 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实现并发控制的资料请关注本站其它相关文章!
JS split切割后出现空字符串怎么过滤
2026-11-05 16:00:30
在 React 或 Vue 项目中封装使用 JS 节流防抖
2026-11-05 15:27:29
JavaScript中怎么将带小数的字符串转为整数?
2026-11-05 14:32:38
怎么用JavaScript策略模式重构电商促销计算逻辑?
2026-11-05 14:01:09
JS使用constructor判断数据类型可靠吗
2026-11-05 13:49:41
