2017-02-15 23 views
2

У меня есть лимит API 10 вызовов в секунду (однако тысячи в день), однако, когда я запускаю эту функцию (вызывается каждый идентификатор стиля объекта,> 10 в секунду):Как ограничить вызовы API в секунду с помощью angular2

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?

ответ

2

У меня есть довольно изящное решение, где вы объединить два наблюдаемых с .zip() operator:

  1. Наблюдаемый излучающий запрос.
  2. Еще один наблюдаемый излучающий значение каждые 0,1 секунды.

Вы в конечном итоге с один наблюдаемых испускающих запросов каждые .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); 
} 

Надеюсь, вы сможете перевести свой код на ваш случай использования. Дайте знать, если у вас появятся вопросы.

UPDATE: Благодаря Адаму, есть также JSBin, показывающий, как задушить запросы, если они не приходят в последовательно (см Convo в комментариях). Он использует оператор concatMap() вместо оператора zip().

+1

Этот подход не решает проблему @ Miguel, если запросы не отправляются быстрее, чем их можно отправить, последовательно.Например, запустите код JSBin, подождите 5 секунд, затем запустите его в консоли JSBin: 'for (let i = 0; i <20; i ++) {getStyleByID (i);}'. Они будут стрелять сразу и не будут дросселировать должным образом, потому что неиспользуемые интервалы хранятся в почтовом индексе, ожидающем использования, вместо того, чтобы отбрасываться, если они не используются немедленно. – Adam

+1

Хороший улов, Адам! Только Мигель может сказать, будет ли мое предложение работать в его случае или нет. Если это не так, как бы вы сделали его пуленепробиваемым? – AngularChef

+0

[Вот JSBin моего решения.] (Http://jsbin.com/lesatebiwo/1/edit?js,console) Единственный недостаток в том, что каждый запрос всегда задерживается на 100 м, включая первый. Не стесняйтесь редактировать свой пост с помощью этого решения, если считаете, что это улучшение. Я нашел решение [здесь] (http://www.g9labs.com/2016/03/21/lossless-rate-limiting-with-rxjs/) – Adam

1

Пока я не тестировал этот код, я бы попробовал что-то в этом направлении.

В основном я создаю переменную, которая отслеживает, когда разрешен следующий запрос. Если это время не прошло, и появится новый запрос, он будет использовать 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; 
    } 
} 
+0

'ERROR in styles.component.ts (47,9): Не удается найти имя« delayUntil ».) styles.component.ts (50,35): Не удается найти имя« delayUntil ».)' – Moshe

+1

Исправлено: попробуйте снова скопировать метод delayRequest. – Adam

2

Вы можете использовать синхронизированную 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, который показывает его в действии

+1

Когда я пытаюсь запустить ваш plnkr, это ошибки в 'boot/app'. 'Ошибка XHR (404) загрузка https: // run.plnkr.co/Zrc7pLEuNlmc8C0p/app/boot.ts'. Я исправил это, изменив строку 'index.html' 25 на' System.import ('./ app/boot') ' – Adam

 Смежные вопросы

  • Нет связанных вопросов^_^