javascript并发请求控制

207 阅读2分钟

一、业务场景

某一天,产品小姐姐让前端老王实现一个批量更新列表数据的需求。老王窃喜,小儿科,后端提供一个更新的接口不就完事儿了。不料,一直配合默契的后端那个哥们儿,刚刚删库跑路了,所以,就把这个接口开发的任务交给了一个新来的开发小弟弟。这个小弟弟比较实诚,三下五除二的就搞定了。然后找到老王说,“王哥哈,这个更新列表的接口写好了,你需要多次调用这个接口。”

“ what?调用一次不行么?” “不行,因为列表批量更新数据比较大,如果其中一个更新失败,会导致这一批的更新失败,客户会投诉咱们滴,所以需要分别更新哈。” “ md,有点儿道理哈。” 突然,产品小姐姐,来了一句,“要尽可能高效的完成批量更新数据呀!,咱们的客户属于小白那种!” 然后,隔壁老王就屁颠屁颠的开始疯狂撸码中。

二、归纳转化

老王苦思冥想,吹了吹掉在桌子上的2根头发,然后喃喃道,原来如此! N个请求,最大并发请求个数M(M <= N),要求尽可能多的每刻执行M个请求

三、实现原理

  1. 创建一个待请求队列池(N),创建一个正在请求队列(M)
  2. 正在请求队列中任意一个请求完成后,通过notify函数发布通知
  1. 把请求结果存储在response中
  2. 已发送请求个数加一
  3. 如果已发送请求个数等于待请求队列总数,则所有请求发送完毕
  1. 外部调用者通过callback函数,拿到最终的结果集合

四、代码解析

/**
 * 并发请求
 * 需求:n个请求,最大并发请求个数m(m <= n),要求尽可能多的每刻执行m个请求。
 */
import { getPluginVersionList } from '../axios/api';

class RequestService {
  /**
   * 待请求队列池【N】
   */
  requestQueue = [];
  /**
   * 最大并发请求个数【M】
   */
  limit = 0;
  /**
   * 并发请求的结果集合
   */
  response = [];
  /**
   * 最大请求总数
   */
  maxRequestCount = 0;
  /**
   * 已发送的请求个数
   */
  sentRequestCount = 0;
  /**
   * 外部调用者回调函数
   */
  callback = null;

  constructor(limit, list) {
    this.limit = limit;
    this.requestQueue = this.getRequestQueue(list);
    this.maxRequestCount = this.requestQueue.length;
  }

  /**
   *批量发送请求
   * @param {Function} callback
   */
  sendRequest(callback) {
    this.callback = callback;
    // 正在请求队列
    for (let i = 0; i < this.limit; i++) {
      const request = this.requestQueue.shift();
      request(this.notify.bind(this));
    }
  }

  /** private method */
  addRequest() {
    const request = this.requestQueue.shift();
    if (request) {
      request(this.notify.bind(this));
    }
  }

  /** private method */
  notify(value) {
    this.sentRequestCount++;

    this.response.push(value);
    this.addRequest();

    if (this.sentRequestCount === this.maxRequestCount) {
      this.callback(this.response);
    }
  }

  /**
   * private method
   * 请求接口封装
   * @param {} param `请求接口参数`
   * @returns Function
   */
  request(param) {
    return function request(notify) {
      getPluginVersionList({ id: param }).then(res => {
        // 通知外部请求接口成功
        notify(res);
      });
    }
  }

  /**
   * private method
   * 待请求队列池
   * @param {Array} list `选中的列表数据`
   * @returns
   */
  getRequestQueue(list) {
    return list.map(item => this.request(item));
  }
}

// 使用举例:
// 假如选中表格中的列表数据250条,任意时刻最大并发请求个数为13;
// 选中的列表数据
const selectedList = Array.from({ length: 250 }, (v, k) => k + 1);
const limit = 13;

const requestService = new RequestService(limit, selectedList);
requestService.sendRequest(function callback(res) {
  console.log(res);
});