如果有100个请求,如何控制并发?

143 阅读2分钟

如果前端需要处理 100 个请求而不希望所有请求同时发出(以控制并发数),可以采用 并发控制 的方式,常用的技术包括:

  1. 使用 Promise.all + 自定义并发队列

通过一个自定义函数控制并发数。

// 并发控制函数
function limitConcurrency(requests, limit) {
  const results = []; // 存储每个请求的结果
  const executing = []; // 当前执行的请求

  for (const request of requests) {
    const p = Promise.resolve().then(() => request());
    results.push(p);

    // 添加到执行队列
    if (limit <= requests.length) {
      const e = p.then(() => executing.splice(executing.indexOf(e), 1));
      executing.push(e);

      // 当并发数达到限制时,等待最早的 Promise 完成
      if (executing.length >= limit) {
        await Promise.race(executing);
      }
    }
  }

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

// 示例请求函数
const createRequest = (id, delay) => () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log(`Request ${id} completed`);
      resolve(`Result ${id}`);
    }, delay);
  });
};

// 构造 100 个请求,每个请求有不同的延迟
const requests = Array.from({ length: 100 }, (_, i) => createRequest(i + 1, Math.random() * 1000));

// 调用并发控制函数,限制最大并发数为 5
limitConcurrency(requests, 5).then((results) => {
  console.log('All requests completed');
  console.log(results);
});

代码说明

  • limitConcurrency 函数:

    • 接受两个参数:

      • requests:由请求函数组成的数组(每个函数返回一个 Promise)。
      • limit:最大并发数。
    • 使用一个执行队列 (executing) 来追踪当前执行的 Promise。

    • 如果并发数达到 limit,通过 Promise.race 等待最早完成的请求。

  • 示例请求:

    • createRequest 模拟请求,使用 setTimeout 模拟不同的延迟。
  • 调用:

    • 通过 limitConcurrency(requests, 5) 将 100 个请求的并发数限制为 5。

  1. 使用第三方库 p-limit

如果想使用现成的工具,p-limit 是一个轻量级的库,专门用于限制并发数。

安装

npm install p-limit

使用示例

import pLimit from 'p-limit';

const limit = pLimit(5); // 设置并发数为 5

// 模拟 100 个请求
const tasks = Array.from({ length: 100 }, (_, i) =>
  limit(() =>
    new Promise((resolve) => {
      setTimeout(() => {
        console.log(`Request ${i + 1} completed`);
        resolve(`Result ${i + 1}`);
      }, Math.random() * 1000);
    })
  )
);

// 执行所有任务
Promise.all(tasks).then((results) => {
  console.log('All requests completed');
  console.log(results);
});