← 中文资源

简体中文版本

P-Limit 任务队列并发控制

用 p-limit 把大批量异步任务约束在固定并发数内运行,避免一次性发出全部请求压垮内存或配额。

阅读英文原文

示例说明

对 500 个 ID 直接 Promise.all 会同时发起 500 个请求,容易触发限流或耗尽句柄。p-limit 返回一个限流器函数,所有任务先进入其内部队列,同一时刻只有 N 个真正执行,后续任务在完成一个后自动补位。既保留了并发的吞吐收益,又把资源占用控制在常数级别。

技术要点

  • 所有任务先进队列,固定 N 个并发执行
  • Promise.all 仍然等待全部任务完成
  • 限流器函数本身是可复用的单一实例

代码解读

syncAll 中每个 fetch 都包在 limit 回调里,map 立即创建全部包装任务但不并发执行,Promise.all 拿到的仍是最终结果数组,顺序与输入一致。第二段用数组长度模拟活跃计数,直观展示并发数始终不超过 2 的补位过程。

使用建议

并发数应结合下游配额与本机资源设定,CPU 密集任务可设为核数。若任务可能失败,配合 Promise.allSettled 收集全部结果而不是整体中断。多个限流器不要互相嵌套,容易造成死锁。

代码示例

import pLimit from 'p-limit';

const limit = pLimit(4);

async function syncAll(ids) {
  const tasks = ids.map((id) =>
    limit(() => fetch(`https://api.example.com/items/${id}`).then((r) => r.json())),
  );
  const results = await Promise.all(tasks);
  return results;
}

const started = [];
const tracker = pLimit(2);
const jobs = [1, 2, 3, 4].map((n) =>
  tracker(async () => {
    started.push(n);
    console.log('running', n, 'active:', started.length);
    await new Promise((r) => setTimeout(r, 100));
    started.pop();
  }),
);
await Promise.all(jobs);