这三个库都用于控制代码执行的频率和并发量,但核心机制不同。p-limit 用于限制同时运行的任务数量(并发度);p-throttle 用于限制单位时间内的调用次数(速率);limiter 提供基于令牌桶算法的底层限流,更偏向 Node.js 服务端场景。前端开发中通常首选 p-* 系列以获得更好的 Promise 支持。
在前端和 Node.js 开发中,控制任务执行的速度和数量是常见需求。limiter、p-limit 和 p-throttle 都解决了这个问题,但它们的适用场景和实现方式有很大区别。让我们从实际工程角度看看它们如何工作。
p-limit 关注的是同时有多少个任务在运行。
// p-limit: 限制同时运行 2 个任务
import pLimit from 'p-limit';
const limit = pLimit(2);
const jobs = Array.from({ length: 5 }).map(() =>
limit(() => fetch('/api/data'))
);
await Promise.all(jobs);
// 同一时刻最多 2 个 fetch 在进行
p-throttle 关注的是单位时间内允许通过多少个任务。
// p-throttle: 限制每秒最多 2 次调用
import pThrottle from 'p-throttle';
const throttle = pThrottle({limit: 2, interval: 1000});
const throttledFetch = throttle(() => fetch('/api/data'));
await throttledFetch();
await throttledFetch();
await throttledFetch(); // 这次会被延迟到下一个时间窗口
limiter 关注的是令牌桶算法。
// limiter: 每秒生成 2 个令牌,桶容量 2
const { RateLimiter } = require('limiter');
const limiter = new RateLimiter(2, 'second');
limiter.removeTokens(1, (err, remaining) => {
// 拿到令牌后才能执行请求
fetch('/api/data');
});
对于前端开发者来说,是否原生支持 Promise 是选择的重要标准。
p-limit 完全基于 Promise 设计。
// p-limit: 原生 Promise 支持
async function run() {
const limit = pLimit(2);
await limit(() => doSomething());
}
p-throttle 同样完全基于 Promise 设计。
// p-throttle: 原生 Promise 支持
async function run() {
const throttle = pThrottle({limit: 1, interval: 1000});
try {
await throttle(() => doSomething());
} catch (e) {
// 直接捕获错误
}
}
limiter 主要基于回调函数(Callback)。
// limiter: 主要基于回调
function run() {
limiter.removeTokens(1, (err, remaining) => {
// 需要在回调里继续逻辑
// 如果要转 Promise 需手动封装
});
}
当任务失败或队列满时,各包的表现不同。
p-limit 会保留所有任务直到执行。
// p-limit: 任务排队,错误隔离
const limit = pLimit(2);
// 即使并发满,新任务也会进入队列等待
await limit(() => riskyOperation());
p-throttle 会延迟执行直到时间窗口允许。
// p-throttle: 自动延迟
const throttle = pThrottle({limit: 1, interval: 1000});
// 调用过快会自动排队延迟执行
await throttle(() => apiCall());
limiter 取决于你如何处理回调。
p-* 系列自动化。// limiter: 手动处理等待
limiter.removeTokens(1, (err, remaining) => {
// 如果没有令牌,回调会阻塞直到可用
// 需要自己处理超时或放弃逻辑
});
你需要上传 50 张图片,但浏览器同时连接数有限,且不想卡死界面。
p-limit// 使用 p-limit
const limit = pLimit(5);
const uploads = images.map(img => limit(() => upload(img)));
await Promise.all(uploads);
第三方 API 规定每秒最多调用 10 次,否则会返回 429 错误。
p-throttle// 使用 p-throttle
const throttle = pThrottle({limit: 10, interval: 1000});
const callApi = throttle(() => fetch('/external-api'));
await callApi();
你在编写一个 API 网关,需要根据用户等级分配不同的令牌速率。
limiter// 使用 limiter
const userLimiter = new RateLimiter(userTokens, 'second');
userLimiter.removeTokens(1, callback);
| 特性 | p-limit | p-throttle | limiter |
|---|---|---|---|
| 控制目标 | 同时运行的任务数 | 单位时间内的调用次数 | 令牌消耗速率 |
| 异步风格 | Promise / async-await | Promise / async-await | 回调函数 (Callback) |
| 主要场景 | 并发控制 (上传/下载) | 速率限制 (API 调用) | 服务端流控 (网关) |
| 前端友好度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ |
| 配置复杂度 | 低 (仅并发数) | 中 (次数 + 时间) | 高 (令牌 + 间隔) |
对于现代前端开发,优先选择 p-limit 或 p-throttle。
p-limit。p-throttle。limiter,因为它的回调风格会增加代码维护成本,且不如 p-* 系列对 Promise 友好。这三个工具都很小巧,但它们解决的是不同维度的问题。选对工具能让你的异步代码更稳定,也更易读。
选择 p-limit 如果你需要限制同时执行的任务数量,例如同时只允许 5 个文件上传或 3 个 API 请求。它非常适合控制并发度,防止浏览器或服务器因过多同时请求而过载。
选择 limiter 如果你在 Node.js 服务端需要实现复杂的令牌桶算法,或者需要精细控制令牌生成速率。由于它主要基于回调函数,不太适合现代前端基于 async/await 的流程,新项目建议优先考虑其他方案。
选择 p-throttle 如果你需要限制单位时间内的调用频率,例如每秒最多调用 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.