async、bottleneck、p-queue、promise-queue 和 queue-promise 都是用于管理 JavaScript 异步任务执行顺序和并发数量的工具。async 是老牌控制流库,提供队列功能但基于回调;bottleneck 专注于速率限制和并发控制,支持分布式场景;p-queue 是现代 Promise 原生队列,轻量且类型友好;promise-queue 和 queue-promise 则是更简单的实现,适合基础需求。它们在 API 设计、维护状态和功能深度上存在显著差异。
在构建高可用的前端或 Node.js 应用时,控制异步任务的并发数量至关重要。无论是为了避免浏览器卡死、防止 API 触发速率限制,还是为了管理资源密集型操作,我们都需要可靠的队列机制。async、bottleneck、p-queue、promise-queue 和 queue-promise 都试图解决这个问题,但它们的侧重点和适用场景截然不同。
async 是老牌的控制流库,它的队列功能基于回调风格,虽然支持 Promise,但基因里带着旧时代的印记。
// async: 基于回调的队列
const queue = async.queue((task, callback) => {
fetchData(task).then(res => callback(null, res));
}, 2); // 并发数为 2
queue.push({ id: 1 });
bottleneck 的核心是限流器(Limiter),它不仅控制并发,还控制时间间隔。
// bottleneck: 专注于限流
const limiter = new Bottleneck({ maxConcurrent: 5, minTime: 100 });
limiter.schedule(() => fetchData());
p-queue 是纯粹的 Promise 队列,设计现代,默认挂起直到添加任务。
// p-queue: Promise 原生
const queue = new PQueue({ concurrency: 5 });
queue.add(() => fetchData());
promise-queue 提供基础的 Promise 排队功能,API 较为简单。
// promise-queue: 基础实现
const queue = new PromiseQueue(5, 100);
queue.add(fetchData());
queue-promise 同样专注于 Promise 队列,强调简洁性。
// queue-promise: 简洁实现
const queue = new Queue();
queue.enqueue(() => fetchData());
这是选择库时的关键分水岭。如果你只关心"同时跑几个",大多数库都能做。如果你关心"每秒跑几个",选择范围会缩小。
async:仅支持并发数(concurrency)。无法直接设置时间间隔。p-queue:支持并发数。可以通过 interval 选项实现简单的速率控制,但不如 bottleneck 精细。bottleneck:同时支持并发数(maxConcurrent)和时间间隔(minTime)。它是唯一原生支持令牌桶算法的库。promise-queue:主要支持并发数。queue-promise:主要支持并发数。// p-queue: 简单的间隔控制
const queue = new PQueue({ concurrency: 1, interval: 1000 });
// bottleneck: 精细的限流
const limiter = new Bottleneck({
maxConcurrent: 5,
minTime: 200 // 任务之间至少间隔 200ms
});
在生产环境中,任务失败是常态。如何处理失败的任务,以及能否监听队列状态,直接影响系统的稳定性。
async 通过回调的 error 参数传递错误,需要手动包装 Promise。
// async: 错误通过回调传递
const queue = async.queue((task, callback) => {
task().then(res => callback(null, res)).catch(err => callback(err));
});
queue.error((err) => console.error(err));
p-queue 直接返回 Promise,错误会 reject,同时提供事件监听。
// p-queue: 原生 Promise 错误处理
queue.add(() => fetchData()).catch(err => console.error(err));
queue.on('error', (err) => console.error(err));
bottleneck 提供丰富的事件系统,包括失败重试逻辑。
// bottleneck: 丰富的事件监听
limiter.on('failed', (error, jobInfo) => {
console.log('Task failed', error);
});
promise-queue 和 queue-promise 通常通过 Promise 的 catch 捕获错误,事件系统较少。
// promise-queue / queue-promise: 基础错误捕获
queue.add(fetchData()).catch(err => console.error(err));
在选择依赖时,库的维护活跃度是隐形成本。
async:维护良好,但属于"遗留技术"。新项目除非已有依赖,否则不建议引入整个库只为用队列。bottleneck:维护活跃,功能复杂,适合后端或重度 API 调用场景。p-queue:维护非常活跃,是 sindresorhus 生态的一部分,TypeScript 支持最好。promise-queue:更新频率较低,功能简单,适合非核心场景。queue-promise:社区规模较小,长期维护性不如 p-queue。⚠️ 注意:对于新项目,
promise-queue和queue-promise虽然没有被官方标记为废弃,但由于p-queue提供了更好的类型支持和活跃度,通常建议优先选择p-queue。
bottleneckconst limiter = new Bottleneck({ maxConcurrent: 5, minTime: 100 });
const results = await Promise.all(urls.map(url => limiter.schedule(() => fetch(url))));
p-queueconst queue = new PQueue({ concurrency: 5 });
files.forEach(file => queue.add(() => upload(file)));
await queue.onIdle();
asyncconst q = async.worker((task, cb) => { /*...*/ }, 5);
q.push(tasks);
queue-promise 或 promise-queueconst queue = new Queue();
queue.enqueue(task1);
queue.enqueue(task2);
| 特性 | async | bottleneck | p-queue | promise-queue | queue-promise |
|---|---|---|---|---|---|
| 核心风格 | 回调 (Callback) | 限流器 (Limiter) | Promise 原生 | Promise 基础 | Promise 基础 |
| 并发控制 | ✅ | ✅ | ✅ | ✅ | ✅ |
| 速率限制 | ❌ | ✅ (强) | ⚠️ (弱) | ❌ | ❌ |
| TS 支持 | ⚠️ (一般) | ✅ | ✅ (优秀) | ⚠️ | ⚠️ |
| 维护状态 | 稳定 ( legacy) | 活跃 | 活跃 | 低频 | 低频 |
| 适用场景 | 旧项目/复杂流 | API 限流/分布式 | 现代通用队列 | 简单脚本 | 简单脚本 |
在现代前端架构中,p-queue 是默认的首选。它在功能、体积和维护性之间取得了最佳平衡。它的 API 设计符合现代 JavaScript 开发者的直觉,且 TypeScript 支持完善,能减少运行时错误。
bottleneck 是特定场景的王者。当你面对严格的 API 速率限制(如 Google Maps API、GitHub API)时,不要尝试用 p-queue 去模拟限流逻辑,直接使用 bottleneck 会更安全、更可靠。
async 应逐步淘汰。除非你正在维护一个深度依赖 async 控制流(如 async.waterfall)的旧系统,否则在新代码中应避免引入它。原生 async/await 配合 p-queue 能提供更清晰的代码结构。
promise-queue 和 queue-promise 需谨慎。它们没有明显的功能缺陷,但生态活跃度远不如 p-queue。在长期维护的企业级项目中,选择社区更活跃的库能降低未来的迁移成本。
bottleneck。p-queue。async。queue-promise,但需评估风险。选择正确的工具不仅能解决当前的并发问题,还能让代码在未来更容易维护和扩展。
如果你的项目遗留了大量回调代码,或者需要复杂的控制流(如 waterfall、series 而不仅是队列),选择 async。它是一个功能全面的工具库,但在新项目中因其回调风格较重,通常不如 Promise 原生库推荐。
类似于 promise-queue,queue-promise 适合非常基础的入队出队需求。如果项目不需要复杂的错误处理或优先级队列,且希望依赖尽可能小,可以使用它,但建议优先评估 p-queue。
如果你需要严格的速率限制(例如调用 API 时每秒不超过 5 次),而不仅仅是并发控制,选择 bottleneck。它支持 Redis 后端,适合分布式节点间的限流场景,功能最强大但也最重。
对于大多数现代前端或 Node.js 项目,选择 p-queue。它是 Promise 原生的,维护活跃,类型定义完善,且 API 简洁。它是 sindresorhus 生态的一部分,适合需要可靠并发控制的通用场景。
如果你需要一个极简单的队列实现,且不需要额外的并发配置或事件监听,可以考虑 promise-queue。但需注意其社区活跃度较低,适合内部工具或对依赖体积极其敏感的非核心场景。

Async is a utility module which provides straight-forward, powerful functions for working with asynchronous JavaScript. Although originally designed for use with Node.js and installable via npm i async, it can also be used directly in the browser. An ESM/MJS version is included in the main async package that should automatically be used with compatible bundlers such as Webpack and Rollup.
A pure ESM version of Async is available as async-es.
For Documentation, visit https://caolan.github.io/async/
For Async v1.5.x documentation, go HERE
// for use with Node-style callbacks...
var async = require("async");
var obj = {dev: "/dev.json", test: "/test.json", prod: "/prod.json"};
var configs = {};
async.forEachOf(obj, (value, key, callback) => {
fs.readFile(__dirname + value, "utf8", (err, data) => {
if (err) return callback(err);
try {
configs[key] = JSON.parse(data);
} catch (e) {
return callback(e);
}
callback();
});
}, err => {
if (err) console.error(err.message);
// configs is now a map of JSON data
doSomethingWith(configs);
});
var async = require("async");
// ...or ES2017 async functions
async.mapLimit(urls, 5, async function(url) {
const response = await fetch(url)
return response.body
}, (err, results) => {
if (err) throw err
// results is now an array of the response bodies
console.log(results)
})