如何实现批量发送请求

225 阅读3分钟

摘要

前端开发中,批量操作是一个比较常见的场景,比如批量删除、新增、审核等等。通常的做法有 :

  1. 通过Promise.all方法同时发送多个请求
  2. 遍历请求数据数组,调用请求方法发送请求

以上两种方式都可以实现批量请求发送,但是无法控制同时请求的数量,一旦请求数量过多那么响应速度将很漫长,对服务端的处理响应的速度也有了更高的要求。

如何实现可控的批量请求?

下面是我实现的一种简单方式,在批量发送请求时控制请求数量。

实现思路

  1. 利用js对象封装一个批量发送请求的工具对象,构造函数接受请求数量限制和具体发送请求的方法,方便代码的复用
  2. 实现可控的批量发送请求功能,有两个核心的点需要考虑:如何发送请求和如何控制同时发送请求的数量。这里在实现的时候使用递归调用的方式来发送请求,然后在工具对象里定义记录并发请求的数量上限和已经发送了的请求的数量属性
  3. 在工具对象新建时指定请求发送的数量上限和请求的具体方法,把实际实现交给使用者,工具对象仅关心请求的发送和限制
  4. 除了发送请求,工具对象里还需要额外的属性请求队列来暂存请求参数,保证在发送处理请求的时候依旧可以不停添加请求
  5. 最后一步就是请求完成的后续操作,需要暴露出方法供使用者设置回调函数和清空请求队列的方法,方便重置工具函数和执行请求完成后的后续操作

上面是简单的实现思路,对于具体使用场景,需要根据场景来实现,这里仅供简单参考。

具体代码示例

在实现的时候使用了typescript的泛型,可根据具体的参数定义和函数定义提供更好的类型提示

type RequestQueue<T extends Record<string,any>,P> = {
  params: T;
  onSuccess?: (response: P) => void;
  onFailure?: (error: any) => void;
};

class ConcurrentRequestManager<T extends Record<string, any>, P> {
  private concurrencyLimit: number;
  private currentRequests: number;
  private queue: RequestQueue<T, P>[];
  private makeRequest: (params: T) => PromiseLike<P>;
  private afterRequestEnd: () => void;
  private hasExecuteRequestEnd: boolean;

  constructor(concurrencyLimit: number, requestFn: (params: T) => PromiseLike<P>) {
    this.concurrencyLimit = concurrencyLimit;
    this.currentRequests = 0;
    this.queue = [];
    this.makeRequest = requestFn;
    this.afterRequestEnd = () => {};
    this.hasExecuteRequestEnd = false;
  }

  // 添加请求到队列
  addRequest(req: RequestQueue<T, P>) {
    this.queue.push(req);
    this.hasExecuteRequestEnd = false;
    this.processQueue();
  }

  setAfterRequestEnd(cb: () => void) {
    this.afterRequestEnd = cb;
  }

  clearQueue() {
    this.queue = [];
  }

  // 处理队列中的请求
  async processQueue() {
    while (this.currentRequests < this.concurrencyLimit && this.queue.length > 0) {
      const req = this.queue.shift()!;
      this.currentRequests++;
      try {
        await this.makeRequest(req.params);
      } catch (error) {
        req.onFailure?.(error);
        break;
      } finally {
        this.currentRequests--;
        this.processQueue(); // 递归调用以处理更多请求
      }
    }

    if (this.queue.length === 0 && !this.hasExecuteRequestEnd) {
      this.hasExecuteRequestEnd = true;
      this.afterRequestEnd();
    }
  }
}

具体使用例子

function makeRequest(params: Record<string, any>) {
  return new Promise((resolve, reject) => {
    // 使用fetch API发送请求,你也可以使用axios或其他库
    fetch('http://localhost:8000/test', {
      body: JSON.stringify(params),
    })
      .then(response => response.blob())
      .then(data => resolve(data))
      .catch(error => reject(error));
  });
}

const paramsArray = [
  {id: 1,name: 'test1'},
  {id: 2,name: 'test2'},
  {id: 3,name: 'test3'},
  {id: 4,name: 'test4'},
  {id: 5,name: 'test5'},
  {id: 6,name: 'test6'},
  {id: 7,name: 'test7'},
  {id: 8,name: 'test8'},
  {id: 9,name: 'test9'},
  {id: 10,name: 'test10'},
  {id: 11,name: 'test11'},
];

const concurrentRequestManager = new ConcurrentRequestManager(5,makeRequest);
concurrentRequestManager.setAfterRequestEnd(() => {
    console.log('请求完成');
  });
paramsArray.forEach(param => {
    concurrentRequestManager.addRequest(param)
});

代码粗陋

这是个人的简单思考,希望大佬们提出意见改进改进