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

作者站长头像
站长
· 阅读数 23

我想在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();
      }
    }
  }
}
回复
1个回答
avatar
test
2024-07-11

根据题目描述,需要 "知道请求是否都完成了",此时可以根据 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 
回复
likes
适合作为回答的
  • 经过验证的有效解决办法
  • 自己的经验指引,对解决问题有帮助
  • 遵循 Markdown 语法排版,代码语义正确
不该作为回答的
  • 询问内容细节或回复楼层
  • 与题目无关的内容
  • “赞”“顶”“同问”“看手册”“解决了没”等毫无意义的内容