p-limit、p-throttle、limiter は、JavaScript 應用における非同期タスクの実行頻度や同時実行数を制御するためのライブラリです。p-limit は同時に実行できるタスクの数を制限し、p-throttle は単位時間あたりの実行回数を制限します。limiter はトークンバケットアルゴリズムを用いて、より柔軟なレート制限を実現します。これらは API 呼び出しの制限や、ブラウザの負荷軽減など、さまざまな場面で活用されます。
JavaScript で非同期処理を扱う際、すべてのタスクを同時に実行すると、サーバーに負荷をかけたり、ブラウザが凍結したりするリスクがあります。p-limit、p-throttle、limiter は、こうした問題を解決するためのツールですが、それぞれ制御の仕組みと目的が異なります。ここでは、実務でどのように使い分けるかを深掘りします。
p-limit は、同時に実行できるプロミスの数を制限します。
import pLimit from 'p-limit';
const limit = pLimit(2); // 同時に 2 つまで
const tasks = [1, 2, 3, 4].map(id =>
limit(() => fetch(`/api/data/${id}`))
);
await Promise.all(tasks);
// 1, 2 が実行され、どちらか完了後に 3 が始まる
p-throttle は、単位時間あたりの実行回数を制限します。
import pThrottle from 'p-throttle';
const throttle = pThrottle({
limit: 2,
interval: 1000 // 1 秒間に 2 回まで
});
const throttledFetch = throttle(() => fetch('/api/data'));
await throttledFetch();
await throttledFetch();
await throttledFetch(); // 1 秒経過するまで待機される
limiter は、トークンバケットアルゴリズムを使用します。
const RateLimiter = require('limiter').RateLimiter;
// 1 秒間に 2 トークン補充、最大 2 個保持
const limiter = new RateLimiter(2, 'second');
limiter.removeTokens(1, (err, remaining) => {
// トークンがあれば即時実行、なければ待機
fetch('/api/data');
});
パッケージによって、関数のラップ方法と戻り値が異なります。
p-limit は、関数をラップして新しい関数を返します。このラップ関数は Promise を返します。p-throttle も同様に関数をラップしますが、時間間隔を内部で管理します。limiter は、クラスインスタンスを作成し、メソッドを呼び出してトークンを取得します。コールバックベースの API が主流ですが、Promise でラップして使うことも多いです。💡 ヒント:
p-limitとp-throttleは現代の Promise/async-await コードに自然にフィットします。limiterは古いスタイルの API なので、ラップ処理が必要になることがあります。
すべてのパッケージで、ラップされた関数自体がエラーを投げた場合、それは通常通り伝播します。
p-limit と p-throttle:
Promise.all などと組み合わせることで、全体の成功・失敗を制御できます。// p-limit の場合
const limit = pLimit(2);
try {
await limit(() => riskyOperation());
} catch (error) {
// エラーをキャッチ可能
}
limiter:
// limiter の場合
limiter.removeTokens(1, (err, remaining) => {
if (err) return;
riskyOperation().catch(err => {
// ここでエラー処理
});
});
100 枚の画像をアップロードしたいが、サーバーに負荷をかけたくない。
p-limitconst limit = pLimit(5);
const uploads = images.map(img => limit(() => upload(img)));
await Promise.all(uploads);
無料枠の API を利用しており、1 秒間に 10 リクエストまでという制限がある。
p-throttleconst throttle = pThrottle({ limit: 10, interval: 1000 });
const callApi = throttle(() => fetch('https://api.example.com'));
瞬間的なアクセス増は許容しつつ、長期的な平均レートを守りたい。
limiterconst limiter = new RateLimiter(10, 'second');
// バースト時にトークンを消費し、落ち着くと回復する
これらのライブラリは便利ですが、以下のような場合は不要です。
for...of ループと await で十分な場合があります。requestAnimationFrame や debounce 関数で済む UI 制御もあります。| 機能 | p-limit | p-throttle | limiter |
|---|---|---|---|
| 制御基準 | 同時実行数 | 時間あたりの回数 | トークンバケット |
| 主な用途 | 並列処理の制限 | API レート制限 | 柔軟なレート制限 |
| API スタイル | Promise / async-await | Promise / async-await | コールバック (主流) |
| バースト対応 | キューで待機 | 間隔を空ける | トークン次第で可能 |
| 依存関係 | ほぼなし | ほぼなし | なし |
選択の基準は、「何を制限したいか」 です。
p-limitp-throttlelimiter現代のフロントエンド開発では、p-limit と p-throttle のほうが Promise との相性が良く、コードも読みやすいため、まずこれらを検討するのが一般的です。limiter は、特定のアルゴリズム要件がある場合や、サーバーサイドの Node.js 應用でレガシーなコードと合わせる場合に検討します。
これらのツールを適切に使うことで、應用の安定性とユーザー体験を向上させられます。
同時に実行するタスクの数を制限したい場合に選択します。例えば、同時に 5 つまでしか API リクエストを送信したくない場合や、リソースを消費する処理を並列化しすぎないようにする場合に適しています。実装がシンプルで、Promise ベースの処理に自然に組み込めます。
トークンバケットアルゴリズムに基づいた制御が必要な場合に選択します。バーストトラフィックを許容しつつ、長期的なレート制限をかけたい場合など、より複雑な制御ロジックが必要なサーバーサイドや特定のクライアント用途に適しています。
単位時間あたりの実行回数を厳密に制限したい場合に選択します。例えば、1 秒間に 10 回までというように、時間ベースの制限が必要な API や、ユーザー操作の頻度を落としたい場合に有効です。間隔を空けて確実に実行したいときに使います。
Run multiple promise-returning & async functions with limited concurrency
Works in Node.js and browsers.
npm install p-limit
import pLimit from 'p-limit';
const limit = pLimit(1);
const input = [
limit(() => fetchSomething('foo')),
limit(() => fetchSomething('bar')),
limit(() => doSomething())
];
// Only one promise is run at once
const result = await Promise.all(input);
console.log(result);
Returns a limit function.
Type: number | object
Minimum: 1
Concurrency limit.
You can pass a number or an options object with a concurrency property.
Type: boolean
Default: false
Reject pending promises with an AbortError when clearQueue() is called.
This is recommended if you await the returned promises, for example with Promise.all, so pending tasks do not remain unresolved after clearQueue().
import pLimit from 'p-limit';
const limit = pLimit({concurrency: 1});
Returns the promise returned by calling fn(...args).
Type: Function
Promise-returning/async function.
Any arguments to pass through to fn.
Support for passing arguments on to the fn is provided in order to be able to avoid creating unnecessary closures. You probably don't need this optimization unless you're pushing a lot of functions.
Warning: Avoid calling the same limit function inside a function that is already limited by it. This can create a deadlock where inner tasks never run. Use a separate limiter for inner tasks.
Process an iterable of inputs with limited concurrency.
The mapper function receives the item value and its index.
Returns a promise equivalent to Promise.all(Array.from(iterable, (item, index) => limit(mapperFunction, item, index))).
This is a convenience function for processing inputs that arrive in batches. For more complex use cases, see p-map.
The number of promises that are currently running.
The number of promises that are waiting to run (i.e. their internal fn was not called yet).
Discard pending promises that are waiting to run.
This might be useful if you want to teardown the queue at the end of your program's lifecycle or discard any function calls referencing an intermediary state of your app.
Note: This does not cancel promises that are already running.
When rejectOnClear is enabled, pending promises are rejected with an AbortError.
This is recommended if you await the returned promises, for example with Promise.all, so pending tasks do not remain unresolved after clearQueue().
Get or set the concurrency limit.
Returns a function with limited concurrency.
The returned function manages its own concurrent executions, allowing you to call it multiple times without exceeding the specified concurrency limit.
Ideal for scenarios where you need to control the number of simultaneous executions of a single function, rather than managing concurrency across multiple functions.
import {limitFunction} from 'p-limit';
const limitedFunction = limitFunction(async () => {
return doSomething();
}, {concurrency: 1});
const input = Array.from({length: 10}, limitedFunction);
// Only one promise is run at once.
await Promise.all(input);
Type: Function
Promise-returning/async function.
Type: object
Type: number
Minimum: 1
Concurrency limit.
Type: boolean
Default: false
Reject pending promises with an AbortError when clearQueue() is called.
This is recommended if you await the returned promises, for example with Promise.all, so pending tasks do not remain unresolved after clearQueue().
See recipes.md for common use cases and patterns.
p-queue package?This package is only about limiting the number of concurrent executions, while p-queue is a fully featured queue implementation with lots of different options, introspection, and ability to pause the queue.