limitReq

实现一个批量请求函数,并限制其并发数,可以通过队列机制信号量来实现。以下是一个基于 Promise 和队列机制的实现,支持用户设置并发限制。


实现代码

function batchRequest(urls, handler, limit) {
  return new Promise((resolve, reject) => {
    const total = urls.length; // 总请求数
    const results = new Array(total); // 存储请求结果
    let completed = 0; // 已完成的请求数
    let activeCount = 0; // 当前活动的请求数
    let currentIndex = 0; // 当前请求的索引

    const next = () => {
      // 如果所有请求已完成,结束批量操作
      if (completed >= total) {
        resolve(results);
        return;
      }

      // 如果达到并发限制或者没有更多请求,直接返回
      if (activeCount >= limit || currentIndex >= total) {
        return;
      }

      // 取出下一个请求
      const index = currentIndex++;
      activeCount++;

      // 执行请求
      handler(urls[index])
        .then((result) => {
          results[index] = result; // 保存结果
        })
        .catch((error) => {
          results[index] = error; // 保存错误
        })
        .finally(() => {
          completed++; // 完成数+1
          activeCount--; // 活动数-1
          next(); // 触发下一个请求
        });

      // 递归触发更多的并发请求
      next();
    };

    // 启动初始的 `limit` 个并发请求
    for (let i = 0; i < limit; i++) {
      next();
    }
  });
}

使用示例

假设我们有一组模拟的请求任务,并希望限制并发数为 3:

// 模拟一个请求函数
function mockRequest(url) {
  return new Promise((resolve) => {
    const time = Math.random() * 2000; // 随机耗时
    setTimeout(() => resolve(`Response from ${url} in ${time.toFixed(0)}ms`), time);
  });
}

// 请求地址
const urls = [
  "https://example.com/1",
  "https://example.com/2",
  "https://example.com/3",
  "https://example.com/4",
  "https://example.com/5",
  "https://example.com/6",
];

// 批量请求
batchRequest(urls, mockRequest, 3).then((results) => {
  console.log("All requests completed:", results);
});

执行结果

  1. 同时最多会有 3 个请求并发。
  2. 请求完成后会按照传入的 urls 顺序返回结果,即使某些请求耗时较长。

输出类似:

All requests completed: [
  "Response from https://example.com/1 in 500ms",
  "Response from https://example.com/2 in 1000ms",
  "Response from https://example.com/3 in 300ms",
  "Response from https://example.com/4 in 200ms",
  "Response from https://example.com/5 in 700ms",
  "Response from https://example.com/6 in 100ms"
]

代码分析

  1. 并发控制:
  • 通过 activeCount 记录当前活跃的请求数,当小于并发限制时,继续启动新的请求。
  1. 结果存储:
  • 通过数组 results 按原始 urls 顺序保存请求结果。
  1. 递归调用:
  • 每次请求完成后,通过调用 next() 启动新请求,确保并发请求始终接近限制。

优化建议

  1. 错误处理:
  • 当前实现中,即使某个请求失败,整体流程也会继续,可以根据需求添加全局错误回调。
  1. 暂停和恢复:
  • 可通过增加状态变量(如 paused)支持暂停和恢复功能。
  1. 请求超时:
  • 可为每个请求添加超时控制,以防止单个请求长时间阻塞队列。

通过这种方式,可以高效地管理并发请求,适用于文件上传、批量任务处理等场景。

使用 class 实现批量请求并限制并发数

class BatchRequest {
  constructor(urls, handler, limit) {
    this.urls = urls; // 请求的 URL 列表
    this.handler = handler; // 请求处理函数
    this.limit = limit; // 限制的并发数
    this.results = []; // 存储请求结果
    this.completed = 0; // 已完成的请求数量
    this.activeCount = 0; // 当前活动的请求数量
    this.currentIndex = 0; // 当前请求的索引
  }

  // 发起单个请求
  async uploadChunk(url, index) {
    try {
      const result = await this.handler(url); // 执行请求
      this.results[index] = result; // 存储结果
    } catch (error) {
      this.results[index] = error; // 存储错误信息
    } finally {
      this.completed++; // 请求完成
      this.activeCount--; // 活跃请求数减少
      this.next(); // 触发下一个请求
    }
  }

  // 启动并发请求
  next() {
    // 如果所有请求已完成,返回结果
    if (this.completed >= this.urls.length) {
      return Promise.resolve(this.results);
    }

    // 如果并发请求数未达到限制或还有更多请求,继续启动请求
    if (this.activeCount < this.limit && this.currentIndex < this.urls.length) {
      const index = this.currentIndex++;
      this.activeCount++;

      this.uploadChunk(this.urls[index], index); // 发起请求

      this.next(); // 递归触发更多的并发请求
    }
  }

  // 启动批量请求
  start() {
    this.next(); // 启动第一个批量请求
  }
}

2. 使用示例

假设我们有一组模拟的请求任务,并希望限制并发数为 3:

// 模拟一个请求函数
function mockRequest(url) {
  return new Promise((resolve) => {
    const time = Math.random() * 2000; // 随机耗时
    setTimeout(() => resolve(`Response from ${url} in ${time.toFixed(0)}ms`), time);
  });
}

// 请求地址列表
const urls = [
  "https://example.com/1",
  "https://example.com/2",
  "https://example.com/3",
  "https://example.com/4",
  "https://example.com/5",
  "https://example.com/6",
];

// 创建批量请求实例
const batchRequest = new BatchRequest(urls, mockRequest, 3);

// 启动请求
batchRequest.start();

// 获取结果
batchRequest.start().then((results) => {
  console.log("All requests completed:", results);
});

3. 代码解析

构造函数 constructor(urls, handler, limit)

  • urls: 需要请求的 URL 列表。
  • handler: 请求的处理函数(如 mockRequest)。
  • limit: 限制的并发请求数。

uploadChunk(url, index):

  • 处理每个请求,并将结果或错误保存到 results 数组中。
  • 请求完成后递减活跃请求数,调用 next() 启动下一个请求。

next():

  • 控制请求的并发数。
  • 当活跃请求数小于 limit 且还有未处理的请求时,会递归调用自己启动新的请求。
  • 当所有请求完成时,返回最终的结果。

start():

  • 启动批量请求,调用 next() 开始控制请求。

4. 优化和扩展

  • 错误处理:我们可以扩展错误处理,保证即使某些请求失败,依然继续其他请求的执行。
  • 进度监控:可以在每个请求完成后,通过回调函数或事件监听器,监控上传进度。
  • 请求超时:为每个请求增加超时机制,防止长时间阻塞。

这个 class 封装了批量请求的逻辑,并支持限制并发数。

苏ICP备2025153828号