js promise如何控制异步并发操作量?

在 JavaScript 中控制异步并发操作量(即限制同时执行的异步任务数量)是优化性能和资源管理的关键。以下是多种实现方法及其详细说明:


一、使用 Promise 和队列控制并发

通过自定义队列系统限制并发数,逐步执行任务。

代码实现

async function asyncPool(poolLimit, tasks) {
  const executing = new Set(); // 记录正在执行的任务
  const results = [];

  for (const task of tasks) {
    // 如果当前并发数已达上限,等待任意一个任务完成
    if (executing.size >= poolLimit) {
      await Promise.race(executing);
    }

    const p = task().then(result => {
      executing.delete(p); // 任务完成后从执行队列移除
      results.push(result);
      return result;
    });

    executing.add(p); // 将任务加入执行队列
  }

  // 等待所有剩余任务完成
  await Promise.all(executing);
  return results;
}

// 使用示例
const tasks = [
  () => fetch('url1'),
  () => fetch('url2'),
  // ...更多任务
];
asyncPool(3, tasks).then(results => {
  console.log('所有任务完成:', results);
});

核心逻辑

  1. 使用 Set 记录正在执行的任务。
  2. 当并发数超过限制时,通过 Promise.race() 等待任意一个任务完成。
  3. 任务完成后更新队列并记录结果。

二、第三方库 p-limit

使用轻量级库 p-limit 快速实现并发控制。

安装与使用

npm install p-limit
import pLimit from 'p-limit';

const limit = pLimit(3); // 最大并发数 3

const tasks = [
  limit(() => fetch('url1')),
  limit(() => fetch('url2')),
  // ...更多任务
];

// 等待所有任务完成
Promise.all(tasks).then(results => {
  console.log(results);
});

优点

  • 代码简洁,无需手动管理队列。
  • 支持动态添加任务。

三、分批次执行(Batch Processing)

将任务拆分成多个批次,逐批执行。

代码实现

async function batchProcess(tasks, batchSize) {
  const results = [];
  for (let i = 0; i < tasks.length; i += batchSize) {
    const batch = tasks.slice(i, i + batchSize);
    const batchResults = await Promise.all(batch.map(task => task()));
    results.push(...batchResults);
  }
  return results;
}

// 使用示例
const tasks = [
  () => fetch('url1'),
  () => fetch('url2'),
  // ...更多任务
];
batchProcess(tasks, 3).then(results => {
  console.log('所有批次完成:', results);
});

缺点

  • 前一批次全部完成后才会执行下一批次,资源利用率较低。

四、基于 Promise 的手动调度

通过递归或循环动态启动任务。

代码实现

function controlledConcurrency(tasks, concurrency) {
  let index = 0;
  const results = [];

  // 递归执行任务
  const runTask = async () => {
    if (index >= tasks.length) return;
    const taskIndex = index++;
    const task = tasks[taskIndex];
    try {
      results[taskIndex] = await task();
    } catch (error) {
      results[taskIndex] = error;
    }
    await runTask(); // 继续执行下一个任务
  };

  // 启动初始的并发任务
  const workers = Array.from(
    { length: Math.min(concurrency, tasks.length) },
    () => runTask()
  );

  return Promise.all(workers).then(() => results);
}

// 使用示例
controlledConcurrency(tasks, 3).then(results => {
  console.log('所有任务完成:', results);
});

五、使用 async-pool

专为并发控制设计的库,支持灵活配置。

安装与使用

npm install async-pool
import asyncPool from 'async-pool';

const timeout = (ms) => new Promise(resolve => setTimeout(resolve, ms));

async function run() {
  const tasks = Array.from({ length: 10 }, (_, i) => () => timeout(1000).then(() => i));
  const results = await asyncPool(3, tasks);
  console.log(results); // [0, 1, 2, 3, ...]
}

run();

六、浏览器环境中的 Promise.withResolvers()(ES2024+)

利用最新的 JavaScript 特性简化代码。

async function controlledConcurrency(tasks, concurrency) {
  const results = [];
  const queue = [...tasks];
  const workers = Array.from({ length: concurrency }, async () => {
    while (queue.length) {
      const task = queue.shift();
      const { promise, resolve, reject } = Promise.withResolvers();
      try {
        const result = await task();
        results.push(result);
        resolve(result);
      } catch (error) {
        reject(error);
      }
      await promise;
    }
  });
  await Promise.all(workers);
  return results;
}

总结

方法 适用场景 优点 缺点
手动队列控制 需要精细控制逻辑 灵活,无依赖 代码复杂度高
p-limit 快速实现并发控制 简洁,社区维护 需安装第三方库
分批次执行 简单分批任务 实现简单 资源利用率低
手动调度 动态任务管理 灵活控制任务启动顺序 需处理递归逻辑
async-pool 需要现成解决方案 功能丰富 需安装第三方库

根据需求选择:

  • 简单场景:使用 p-limit 或分批次执行。
  • 精细控制:手动队列或递归调度。
  • 最新特性:ES2024 的 Promise.withResolvers()
posted @ 2025-03-22 18:09  卓能文  阅读(171)  评论(0)    收藏  举报