async、bottleneck、p-queue、promise-queue、queue-promise は、JavaScript 環境における非同期処理の制御、タスクのキューイング、および API レート制限を管理するためのライブラリです。async は最も歴史が古く、コレクション操作やフロー制御を含む汎用非同期ユーティリティを提供します。bottleneck はレート制限と負荷分散に特化しており、API 呼び出しの制限を厳密に守りたい場合に適しています。p-queue は Promise ベースのシンプルで軽量なキュー実装で、並行処理数の制御に優れています。promise-queue と queue-promise はよりシンプルなキュー実装ですが、メンテナンス状況や機能面で違いがあります。これらはすべて、ブラウザと Node.js の両方で動作しますが、モダンな Promise ベースのプロジェクトでは p-queue や bottleneck が推奨される傾向にあります。
フロントエンドおよび Node.js 開発において、非同期処理の順序制御、並行処理数の制限、API レート制限の管理は重要な課題です。async、bottleneck、p-queue、promise-queue、queue-promise はそれぞれ異なるアプローチでこの問題に対処します。本記事では、実務での選択基準を明確にするために、これら 5 つのパッケージを技術的に深く比較します。
各ライブラリは異なる設計思想を持っており、それが機能セットと使用场景に影響します。
async は 2010 年頃から存在する最も歴史のあるライブラリで、非同期コレクション操作とフロー制御を包括的に提供します。Promise 以前のコールバック時代から進化し、現在は Promise ベースの API も提供しています。
// async: 非同期 map 処理
import { map } from 'async';
const results = await map(['file1.txt', 'file2.txt'], async (file) => {
return await readFile(file);
});
bottleneck はレート制限と負荷分散に特化したライブラリです。単なるキューではなく、時間ベースの制限(例:1 秒間に 5 リクエスト)を厳密に管理します。
// bottleneck: レート制限付き実行
import Bottleneck from 'bottleneck';
const limiter = new Bottleneck({ minTime: 200, maxConcurrent: 5 });
const result = await limiter.schedule(() => fetch('/api/data'));
p-queue は Promise ベースの軽量キューで、並行処理数の制御に焦点を当てています。Sindre Sorhus 氏によってメンテナンスされており、モダンな JavaScript プロジェクトとの親和性が高いです。
// p-queue: 並行処理制限
import PQueue from 'p-queue';
const queue = new PQueue({ concurrency: 5 });
await queue.add(() => fetch('/api/item/1'));
await queue.add(() => fetch('/api/item/2'));
promise-queue と queue-promise はよりシンプルな FIFO キュー実装で、機能は限定的ですが、特定のユースケースでは十分です。
// promise-queue: 基本的なキュー
import PromiseQueue from 'promise-queue';
const queue = new PromiseQueue(5, 1); // 5 並行、無限待機
queue.add(() => fetch('/api/data'));
// queue-promise: シンプル実装
import Queue from 'queue-promise';
const queue = new Queue();
queue.add(() => fetch('/api/data'));
並行処理数を制限する方法は、ライブラリによって大きく異なります。
| パッケージ | 並行制御 | レート制限 | 優先度 | 分散対応 |
|---|---|---|---|---|
async | ❌ (mapSeries 等) | ❌ | ❌ | ❌ |
bottleneck | ✅ | ✅ | ✅ | ✅ (Redis) |
p-queue | ✅ | ❌ | ✅ | ❌ |
promise-queue | ✅ | ❌ | ❌ | ❌ |
queue-promise | ❌ | ❌ | ❌ | ❌ |
async は並行処理制御というより、コレクション操作の非同期バージョンを提供します。parallel、series、waterfall などのフロー制御関数がありますが、動的な並行数制限はできません。
// async: 並列実行(全タスク同時開始)
import { parallel } from 'async';
await parallel([
(cb) => setTimeout(() => cb(null, 1), 100),
(cb) => setTimeout(() => cb(null, 2), 100)
]);
bottleneck は最も高度な制御を提供し、minTime(タスク間の最小時間)と maxConcurrent(最大並行数)の両方を設定できます。
// bottleneck: 詳細なレート制限
const limiter = new Bottleneck({
maxConcurrent: 5, // 最大 5 並列
minTime: 100, // タスク間 100ms 以上
highWater: 100, // キューの警告閾値
strategy: Bottleneck.strategy.OVERFLOW // 超過時の戦略
});
p-queue は concurrency オプションで並行処理数を制限しますが、時間ベースのレート制限は built-in で提供していません。
// p-queue: 並行数制限のみ
const queue = new PQueue({
concurrency: 5,
interval: 1000, // 追加機能:時間ベース制限も可能
intervalCap: 10 // 1 秒間に 10 タスク
});
promise-queue はコンストラクタで並行数と待機時間を指定しますが、柔軟性は低いです。
// promise-queue: 固定設定
const queue = new PromiseQueue(
5, // 並行処理数
1000 // 待機時間(使用されない場合あり)
);
queue-promise は最もシンプルで、並行処理制御機能は限定的または存在しません。
// queue-promise: 基本的なキュー
const queue = new Queue();
// 並行制御オプションは限定的
プロダクション環境では、エラーハンドリングの挙動が重要です。
async は伝統的に Node.js のエラーファーストコールバックパターンに従いますが、Promise ベースの API も提供しています。エラー発生時の挙動は関数によって異なります。
// async: エラーハンドリング
import { map } from 'async';
try {
const results = await map(files, async (file) => {
if (file === 'bad.txt') throw new Error('Failed');
return await readFile(file);
});
} catch (error) {
// 最初のエラーで停止
console.error(error);
}
bottleneck はエラー発生後もキューを継続でき、再試行ロジックを組み込むことができます。
// bottleneck: エラー後の継続
limiter.schedule(async () => {
try {
return await api.call();
} catch (error) {
// エラーを握りつぶして継続
return null;
}
});
p-queue は Promise の標準的なエラーハンドリングに従い、個別のタスクエラーがキュー全体を停止させることはありません。
// p-queue: 個別タスクのエラー処理
queue.add(async () => {
try {
return await fetch('/api/data');
} catch (error) {
console.error('Task failed:', error);
return null; // キューは継続
}
});
promise-queue と queue-promise も同様に Promise の標準挙動に従いますが、エラー回復の機能は限定的です。
// promise-queue: エラー処理
queue.add(() => {
return fetch('/api/data').catch(err => {
console.error(err);
return null;
});
});
バンドルサイズを気にするフロントエンドプロジェクトでは、依存関係の少なさが重要です。
async: 依存関係なし、ただしコードベースが大きいbottleneck: 依存関係なし、中程度のサイズp-queue: 依存関係あり(eventemitter3)、軽量promise-queue: 依存関係なし、非常に軽量queue-promise: 依存関係なし、軽量// インポート例の比較
import { map } from 'async'; // 全体または個別
import Bottleneck from 'bottleneck'; // デフォルトエクスポート
import PQueue from 'p-queue'; // デフォルトエクスポート
import PromiseQueue from 'promise-queue'; // デフォルトエクスポート
import Queue from 'queue-promise'; // デフォルトエクスポート
すべてのパッケージがブラウザと Node.js の両方で動作しますが、注意点があります。
async は最も広い互換性を持ち、古いブラウザでも動作しますが、ポリフィルが必要な場合があります。
bottleneck は Redis アダプターを使用する場合、Node.js 環境でのみ完全な機能を利用できます。ブラウザではローカルモードになります。
// bottleneck: Redis 連携(Node.js のみ)
const limiter = new Bottleneck({
datastore: 'redis',
redisClient: new Redis()
});
p-queue はブラウザと Node.js の両方で完全に動作し、モダンな JavaScript 機能を使用しています。
promise-queue と queue-promise も両環境で動作しますが、機能セットが限定的です。
パッケージの長期的な使用を考えると、メンテナンス状況が重要です。
async: 非常に活発にメンテナンスされており、事実上の標準ライブラリbottleneck: 活発にメンテナンスされており、企業でも使用されているp-queue: 活発にメンテナンスされており、Sindre Sorhus 氏のエコシステムの一部promise-queue: メンテナンス頻度は低く、新機能追加は少ないqueue-promise: メンテナンス状況を確認が必要、機能追加は限定的外部 API を呼び出す際に、レート制限(例:1 分間に 100 リクエスト)を厳守する必要がある場合:
✅ 推奨: bottleneck
const limiter = new Bottleneck({
minTime: 600, // 600ms ごとに 1 リクエスト
maxConcurrent: 1
});
const results = await Promise.all(
urls.map(url => limiter.schedule(() => fetch(url)))
);
複数の画像を並列でアップロードしたいが、ブラウザを重くしたくない場合:
✅ 推奨: p-queue
const queue = new PQueue({ concurrency: 5 });
const uploadJobs = images.map(img =>
queue.add(() => uploadImage(img))
);
await Promise.all(uploadJobs);
既存のコードベースで async が既に使用されており、新しい機能追加が必要な場合:
✅ 推奨: async(一貫性の維持)
import { series } from 'async';
await series([
(cb) => migrateDatabase(cb),
(cb) => clearCache(cb),
(cb) => restartService(cb)
]);
非常にシンプルなキュー機能のみが必要で、依存関係を増やしたくない場合:
✅ 推奨: promise-queue または queue-promise
const queue = new PromiseQueue(1, Infinity);
queue.add(() => processItem(item1));
queue.add(() => processItem(item2));
| 機能 | async | bottleneck | p-queue | promise-queue | queue-promise |
|---|---|---|---|---|---|
| 並行制御 | 限定的 | ✅ 高度 | ✅ 簡易 | ✅ 簡易 | ❌ |
| レート制限 | ❌ | ✅ 高度 | ⚠️ 簡易 | ❌ | ❌ |
| 優先度キュー | ❌ | ✅ | ✅ | ❌ | ❌ |
| 分散対応 | ❌ | ✅ (Redis) | ❌ | ❌ | ❌ |
| Promise 対応 | ✅ | ✅ | ✅ | ✅ | ✅ |
| 保守状況 | ✅ 活発 | ✅ 活発 | ✅ 活発 | ⚠️ 低 | ⚠️ 要確認 |
| バンドルサイズ | 大 | 中 | 小 | 最小 | 小 |
新規プロジェクトでは、p-queue または bottleneck の使用を推奨します。これらはモダンな Promise ベースの API を提供し、活発にメンテナンスされています。
bottleneckp-queueasyncpromise-queueレガシープロジェクトでは、既存のコードベースとの一貫性を保つために、既に使用されているライブラリを継続使用することが賢明です。
最終的に、プロジェクトの要件(レート制限、並行制御、保守性)とチームの習熟度を考慮して選択してください。どのライブラリも適切に使用すれば、非同期処理の制御を大幅に改善できます。
async は既に大規模なコードベースで使用されている場合や、コレクション操作(map、filter、reduce など)の非同期バージョンを包括的に必要とする場合に選択します。ただし、新規プロジェクトではモダンな Promise ベースの代替案を検討すべきです。
基本的な Promise キュー機能が必要ですが、p-queue の機能が多すぎる場合に検討できます。ただし、メンテナンス状況をよく確認してから使用してください。
API レート制限を厳密に遵守する必要がある場合や、複雑な負荷分散ロジックが必要な場合に bottleneck を選択します。Redis 連携による分散環境でのレート制限にも対応しています。
シンプルで軽量な Promise キューが必要な場合、または並行処理数を制限したい場合に p-queue を選択します。モダンなコードベースとの親和性が高く、メンテナンスも活発です。
非常にシンプルな FIFO キュー機能のみが必要で、追加の機能や活発なメンテナンスを必要としないレガシープロジェクトでのみ検討します。新規プロジェクトでは非推奨です。

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