js 如何给一个class 函数添加触发事件,外部可以监听到呢?

我想在LimitRequest中去限制并发请求数量,在外部想要知道请求是否都完成了,就要监听到currentSum的大小。如何实现呢?

export class LimitRequest {
  private limit: number = 1; // 限制并发数量
  private currentSum: number = 0; // 当前发送数量
  private requests: Array<any> = []; // 请求
  constructor(limit: number) {
    this.limit = limit;
    this.currentSum = 0;
    this.requests = [];
  }

  public request(reqFn: Function) {
  
    this.requests.push(reqFn);
    if (this.currentSum < this.limit) {
      this.run();
    }
  }
  public stop() {
    this.requests = [];
    this.currentSum = 0;
  }
  async run() {
   
    try {
      ++this.currentSum;
      const fn = this.requests.shift();
      console.log('开始开始', this.currentSum, this.requests.length);
      await fn();
    } catch (err) {
      console.log('Error', err);
    } finally {
      --this.currentSum;
      if (this.requests.length > 0) {
        this.run();
      }
    }
  }
}
阅读 2.7k
3 个回答

根据题目描述,需要 "知道请求是否都完成了",此时可以根据 requests.length 是否为 0 来判断,即如果请求列表为空时代表所有请求均已被执行。

LimitRequest.ts

export default class LimitRequest {
  private limit: number = 1; // 限制并发数量
  private currentSum: number = 0; // 当前发送数量
  private requests: Array<any> = []; // 请求
  private finishedFn: Function = () => {}; // 请求完成后的回调函数
  constructor(limit: number, finishedFn = () => {}) {
    this.limit = limit;
    this.currentSum = 0;
    this.requests = [];
    // 设置 finishedFn
    this.finishedFn = finishedFn;
  }

  public request(reqFn: Function) {
    this.requests.push(reqFn);
    if (this.currentSum < this.limit) {
      this.run();
    }
  }
  public stop() {
    this.requests = [];
    this.currentSum = 0;
  }
  async run() {
    try {
      ++this.currentSum;
      const fn = this.requests.shift();
      console.log("开始", this.currentSum, this.requests.length);
      await fn();
    } catch (err) {
      console.log("Error", err);
    } finally {
      --this.currentSum;
      if (this.requests.length > 0) {
        this.run();
      } else {
        // 如果请求列表为空,执行 finishedFn
        this.finishedFn();
      }
    }
  }
}

用法:

import LimitRequest from "./LimitRequest";

const instance = new LimitRequest(2, () => {
  console.log("Finished");
});

const fn1 = function () {
  console.log("fn1");
};

const fn2 = async function () {
  console.log("fn2");
  return "fn2";
};

const fn3 = function () {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("fn3");
      resolve("fn3");
    }, 5000);
  });
};

const fn4 = function () {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("fn4");
      reject("fn4");
    }, 5000);
  });
};

const fn5 = function () {
  console.log("fn5");
  return function () {
    return "fn5";
  };
};

const fn6 = function () {
  console.log("fn6");
  return "fn6";
};

instance.request(fn1);
instance.request(fn2);
instance.request(fn3);
instance.request(fn4);
instance.request(fn5);
instance.request(fn6);

输出如下:

开始 1 0
fn1 
开始 2 0
fn2 
开始 2 3
开始 2 2
fn3 
开始 2 1
fn5 
开始 2 0
fn6 
Finished 
fn4 
Error fn4 
Finished 

上面的实现有个小问题,Finished 打印了两次,即结束回调函数执行了两次。这是由于我们仅仅判断了 requests.length 是否为空,而没有判断传入的函数是否均已执行完成,异步函数的执行会有延迟。要解决这个问题,可以在

  1. LimitRequest 这个类中为每一个传入的请求函数增加一个唯一的 id,用于识别每个函数。
  2. 新增一个属性 pendingRequests 记录执行中的函数的 id,在请求函数执行完毕后再从 pendingRequests 中删除其 id。
  3. 最后函数执行完毕后判断 pendingRequestsrequests 均为空,即可以得知所有请求执行完成。

实现:

export default class LimitRequest {
  private limit: number = 1; // 限制并发数量
  private currentSum: number = 0; // 当前发送数量
  private requests: Array<any> = []; // 请求
  private finishedFn: Function = () => {}; // 请求完成后的回调函数
  private pendingRequests = new Set(); // Set 存储正在执行中的请求的 id
  private fnId = 0; // 自增的 id,用于唯一识别传入的函数
  constructor(limit: number, finishedFn = () => {}) {
    this.limit = limit;
    this.currentSum = 0;
    this.requests = [];
    // 设置 finishedFn
    this.finishedFn = finishedFn;
  }

  public request(reqFn: Function) {
    const fnId = this.fnId;
    // @ts-ignore
    reqFn.id = fnId; // 为请求函数分配一个 id
    this.requests.push(reqFn);
    this.pendingRequests.add(fnId); // 把此 id 加入 Set
    this.fnId++; // id 自增
    if (this.currentSum < this.limit) {
      this.run();
    }
  }
  public stop() {
    this.requests = [];
    this.currentSum = 0;
    this.pendingRequests.clear(); // 清空执行中函数的 id 列表
  }
  async run() {
    let fn: any;
    //console.log("this.pendingRequests:", this.pendingRequests);
    try {
      ++this.currentSum;
      fn = this.requests.shift();
      console.log("开始", this.currentSum, this.requests.length);
      await fn();
    } catch (err) {
      console.log("Error", err);
    } finally {
      // 当前函数执行完毕后,从 Set 中删除其 id
      this.pendingRequests.delete(fn.id);
      --this.currentSum;
      if (this.requests.length > 0) {
        this.run();
      } else {
        // 如果 Set 为空,说明所有请求已执行完成
        if (this.pendingRequests.size === 0) {
          // 如果请求列表为空,执行 finishedFn
          this.finishedFn();
        }
      }
    }
  }
}

输出:

开始 1 0
fn1 
开始 2 0
fn2 
开始 2 3
开始 2 2
fn3 
开始 2 1
fn5 
开始 2 0
fn6 
fn4 
Error fn4 
Finished 

最简单也是最优雅的方式是使用 RxJS 处理,同时也可以简化请求逻辑。

我比较推荐的做法是 extends EventEmitter3 这个仓库,使这个类具备 .on().off() 等兼容 node.js EventEmiter 类接口的能力。

然后就用一般的注册事件、广播事件即可。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题