【问题标题】:How to limit API calls per second with angular2如何使用 angular2 限制每秒 API 调用次数
【发布时间】:2017-07-04 18:12:18
【问题描述】:

我的 API 限制为每秒 10 次调用(但每天数千次),但是,当我运行此函数时(调用对象的每个样式 ID,> 每秒 10 次):

  getStyleByID(styleID: number): void {
    this._EdmundsAPIService.getStyleByID(styleID).subscribe(
      style => {this.style.push(style); },
      error =>  this.errorMessage = <any>error);
  }

从此函数(仅 1 次调用,使用 onInit):

  getStylesWithoutYear(): void {
    this._EdmundsAPIService.getStylesWithoutYear(this.makeNiceName, this.modelNiceName, this.modelCategory)
      .subscribe(
        styles => { this.styles = styles;
                      this.styles.years.forEach(year =>
                        year.styles.forEach(style =>
                          this.getStyleByID(style.id)));

        console.log(this.styles); },
        error =>  this.errorMessage = <any>error);
  }

它每秒发出超过 10 次调用。如何限制或减慢这些调用以防止出现 403 错误?

【问题讨论】:

    标签: rest angular angular-services


    【解决方案1】:

    我有一个非常简洁的解决方案,您将两个可观察对象.zip() operator结合起来:

    1. 发出请求的可观察对象。
    2. 另一个 observable 每 0.1 秒发出一个值。

    您最终会每 0.1 秒发出一个可观察到的发出请求(= 每秒 10 个请求)。

    这是代码 (JSBin):

    // Stream of style ids you need to request (this will be throttled).
    const styleIdsObs = new Rx.Subject<number>();
    
    // Getting a style means pushing a new styleId to the stream of style ids.
    const getStyleByID = (id) => styleIdsObs.next(id);
    
    // This second observable will act as the "throttler".
    // It emits one value every .1 second, so 10 values per second.
    const intervalObs = Rx.Observable.interval(100);
    
    Rx.Observable
      // Combine the 2 observables. The obs now emits a styleId every .1s. 
      .zip(styleIdsObs, intervalObs, (styleId, i) => styleId)
      // Get the style, i.e. run the request.
      .mergeMap(styleId => this._EdmundsAPIService.getStyleByID(styleId))
      // Use the style.
      .subscribe(style => {
        console.log(style);
        this.style.push(style);
      });
    
    // Launch of bunch of requests at once, they'll be throttled automatically.
    for (let i=0; i<20; i++) {
      getStyleByID(i);
    }
    

    希望您能够将我的代码翻译成您自己的用例。如果您有任何问题,请告诉我。

    更新:感谢 Adam,还有一个 JSBin 展示了如果请求不一致时如何限制请求(请参阅 cmets 中的 convo)。它使用concatMap() 运算符而不是zip() 运算符。

    【讨论】:

    • 这种方法不能解决@Miguel 的问题,除非请求的进入速度快于它们的发送速度。例如,运行您的 JSBin 代码,等待 5 秒,然后在 JSBin 控制台中运行它:for (let i=0; i&lt;20; i++) {getStyleByID(i);}。它们将立即全部触发,并且不会正确节流,因为未使用的间隔存储在 zip 中等待使用,而不是在不立即使用时被丢弃。
    • 很好,亚当!只有 Miguel 可以判断我的建议是否适用于他的情况。如果没有,你如何让它防弹?
    • Here's the JSBin of my solution. 唯一的缺点是每个请求总是延迟100m,包括第一个。如果您认为这是一种改进,请随时使用此解决方案编辑您的帖子。我找到了解决方案here
    • 谢谢你,亚当。我已通过指向您的垃圾箱的链接更新了答案。
    【解决方案2】:

    您可以使用每 n 毫秒触发一次的定时Observable。我没有修改你的代码,但这个显示了它是如何工作的:

    someMethod() {
      // flatten your styles into an array:
      let stylesArray = ["style1", "style2", "style3"];
    
      // create a scheduled Observable that triggers each second
      let source = Observable.timer(1000,1000);
      // use a counter to track when all styles are processed
      let counter = 0;
    
      let  subscription = source.subscribe( x => {
        if (counter < stylesArray.length) {
            // call your API here
            counter++;
        } else {
            subscription.complete();
        }
      });
    }
    

    在这里找到一个plunk 来展示它的实际效果

    【讨论】:

    • 当我尝试运行您的 plnkr 时,它在 boot/app 上出错。 XHR error (404) loading https://run.plnkr.co/Zrc7pLEuNlmc8C0p/app/boot.ts。我通过将 index.html 第 25 行更改为 System.import('./app/boot') 来修复它
    【解决方案3】:

    虽然我没有测试这段代码,但我会尝试这些方面的东西。

    基本上,我创建了一个变量来跟踪何时允许发出下一个请求。如果该时间尚未过去,并且有新请求进来,它将使用setTimeout 允许该函数以适当的时间间隔运行。如果delayUntil 的值是过去的,那么请求可以立即运行,并且还可以将定时器从当前时间推后100毫秒。

    delayUntil = Date.now();
    
    getStylesWithoutYear(): void {
      this.delayRequest(() => {
        this._EdmundsAPIService.getStylesWithoutYear(this.makeNiceName, this.modelNiceName, this.modelCategory)
          .subscribe(
            styles => { this.styles = styles;
                        this.styles.years.forEach(year =>
                          year.styles.forEach(style =>
                            this.getStyleByID(style.id)));
    
            console.log(this.styles); },
            error =>  this.errorMessage = <any>error);
      };        
    }
    
    delayRequest(delayedFunction) {
      if (this.delayUntil > Date.now()) {
        setTimeout(delayedFunction, this.delayUntil - Date.now());
        this.delayUntil += 100;
      } else {
        delayedFunction();
        this.delayUntil = Date.now() + 100;
      }
    }
    

    【讨论】:

    • ERROR in styles.component.ts (47,9): Cannot find name 'delayUntil'.) styles.component.ts (50,35): Cannot find name 'delayUntil'.)
    • 修复了,尝试再次复制delayRequest方法。
    猜你喜欢
    • 2018-02-16
    • 2017-05-19
    • 2018-08-26
    • 2012-02-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-27
    • 1970-01-01
    相关资源
    最近更新 更多