高并发场景下 Promise.all 限制并发数量怎么实现队列控制?

文章导读
Promise.all 本身不支持并发限制,所有传入的 Promise 会立即执行。在高并发场景下,建议引入 p-limit 库或手动实现异步队列来限制同时运行的任务数量,避免耗尽系统资源。
📋 目录
  1. 环境兼容与导入方式
  2. 方案一:使用 p-limit 库(推荐)
  3. 方案二:手动实现并发队列
  4. 自动化验证并发限制
  5. 常见坑与排查
A A

Promise.all 本身不支持并发限制,所有传入的 Promise 会立即执行。在高并发场景下,建议引入 p-limit 库或手动实现异步队列来限制同时运行的任务数量,避免耗尽系统资源。

先说结论:原生 Promise.all 无法控制并发,必须通过外部队列机制拦截任务提交。

  • 适合:IO 密集型任务批量处理,如文件读写、网络请求(Node.js 与浏览器均适用)
  • 先看:任务失败是否会导致整体中断,决定用 all 还是 allSettled
  • 建议:优先使用成熟库而非手写队列,减少边界条件错误

环境兼容与导入方式

p-limit 支持 Node.js 和现代浏览器环境。根据项目模块规范选择导入方式,避免语法报错:

高并发场景下 Promise.all 限制并发数量怎么实现队列控制?
// ESM 模式 (package.json 中 type: module 或 .mjs 文件)
import pLimit from 'p-limit';

// CommonJS 模式 (Node.js 默认)
const pLimit = require('p-limit');

方案一:使用 p-limit 库(推荐)

这是最稳健的方案。注意任务必须是函数,不能是 Promise 实例,否则传入时就会立即执行。

高并发场景下 Promise.all 限制并发数量怎么实现队列控制?

1. 基础并发控制

const limit = pLimit(5); // 限制并发数为 5

const tasks = urls.map(url => limit(() => fetch(url)));
Promise.all(tasks).then(results => {
  console.log('完成');
});

2. 配合 Promise.allSettled 处理错误

避免单个任务失败导致整体中断,建议结合 allSettled 使用,并在任务内部捕获异常:

const tasks = urls.map(url => 
  limit(async () => {
    try {
      const res = await fetch(url);
      return { status: 'fulfilled', value: res };
    } catch (err) {
      return { status: 'rejected', reason: err };
    }
  })
);

const results = await Promise.allSettled(tasks);
// 后续处理 results 数组

方案二:手动实现并发队列

如果不希望引入依赖,可以实现一个简单的并发控制器。核心逻辑是维护一个运行计数器和一个等待队列。

高并发场景下 Promise.all 限制并发数量怎么实现队列控制?
function createConcurrencyQueue(concurrency) {
  let activeCount = 0;
  const queue = [];

  const next = () => {
    activeCount--;
    if (queue.length > 0) {
      const task = queue.shift();
      task();
    }
  };

  return (taskFn) => {
    return new Promise((resolve, reject) => {
      const run = async () => {
        activeCount++;
        try {
          const result = await taskFn();
          resolve(result);
        } catch (error) {
          reject(error);
        } finally {
          next();
        }
      };

      if (activeCount < concurrency) {
        run();
      } else {
        queue.push(run);
      }
    });
  };
}

// 使用示例
const queue = createConcurrencyQueue(5);
const tasks = urls.map(url => queue(() => fetch(url)));
Promise.all(tasks);

自动化验证并发限制

不要仅靠肉眼观察日志,可以通过计数器断言验证并发是否超标。以下脚本可在 Node.js 环境中直接运行测试:

let maxConcurrent = 0;
let currentConcurrent = 0;
const limit = pLimit(5);

const tasks = Array.from({ length: 20 }, (_, i) => 
  limit(async () => {
    currentConcurrent++;
    if (currentConcurrent > maxConcurrent) {
      maxConcurrent = currentConcurrent;
    }
    
    // 模拟耗时
    await new Promise(r => setTimeout(r, 100)); 
    
    currentConcurrent--;
  })
);

await Promise.all(tasks);

console.log(`设定并发:5,实际最大并发:${maxConcurrent}`);
if (maxConcurrent > 5) {
  console.error('验证失败:并发数超出限制');
} else {
  console.log('验证通过');
}

常见坑与排查

  • 错误处理:Promise.all 遇到单个 reject 会立即中断。若需忽略个别错误,需在任务内部 try-catch 或使用 Promise.allSettled。
  • 内存泄漏:如果队列无限增长且消费速度慢,需确保任务有超时机制,避免队列堆积。
  • 上下文丢失:异步队列可能导致 this 指向变化,建议使用箭头函数或显式 bind。
  • 环境差异:浏览器环境需注意 CORS 和域名并发限制(通常 6 个),Node.js 需注意文件描述符限制。