Angular / RXJS - Отслеживание HTTP-запросов, застрявших в режиме повтора - PullRequest
6 голосов
/ 16 октября 2019

У меня есть функция, которая обрабатывает несколько запросов API для меня и переводит каждый запрос в режим повтора, если не удается. Теперь, если один запрос уже находится в цикле повтора и поступает другой экземпляр того же вызова API, моя функция не может отследить это и снова добавить избыточный вызов API в цикл повторения.

Assuming i am placing a call to
/api/info/authors

What is happening

1stREQ| [re0]------>[re1]------>[re2]------>[re3]------>[re4]------>[re5]
2ndREQ|                         [re0]------>[re1]------>[re2]------>[re3]------>[re4]------>[re5]


What should happen,

1stREQ| [re0]------>[re1]------>[re2]------>[re3]------>[re4]------>[re5]
2ndREQ|                         [re0]/ (MERGE)

Послемой сервис совпадает с моей функцией Retry,

import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { retryWhen, mergeMap, finalize, share, shareReplay } from 'rxjs/operators';
import { Observable, throwError, of, timer } from 'rxjs';


@Injectable({
  providedIn: 'root'
})
export class DataService {
  constructor(private http: HttpClient) { }

  private apiUrl: string = 'http://localhost/api-slim-php/public/api';
  public dataServerURL: string = 'http://localhost/';

  /* 
  This function fetches all the info from API /info/{category}/{id}
  category  : author    & id  : '' or 1,2,3... or a,b,c...
  category  : form      & id  : '' or 1,2,3...
  category  : location  & id  : '' or 1,2,3...
  category  : school    & id  : '' or 1,2,3...
  category  : timeframe & id  : '' or 1,2,3...
  category  : type      & id  : '' or 1,2,3...
  */
  public getInfoAPI(category: string, id: string = "", page: string = "1", limit: string = "10") {
    var callURL: string = '';

    if (!!id.trim() && !isNaN(+id)) callURL = this.apiUrl + '/info/' + category + '/' + id;
    else callURL = this.apiUrl + '/info/' + category;

    return this.http.get(callURL, {
      params: new HttpParams()
        .set('page', page)
        .set('limit', limit)
    }).pipe(
      retryWhen(genericRetryStrategy({ maxRetryAttempts: 5, scalingDuration: 1000 })),
      shareReplay()
    );
  }
}
export const genericRetryStrategy = ({
  maxRetryAttempts = 3,
  scalingDuration = 1000,
  excludedStatusCodes = []
}: {
  maxRetryAttempts?: number,
  scalingDuration?: number,
  excludedStatusCodes?: number[]
} = {}) => (attempts: Observable<any>) => {
  return attempts.pipe(
    mergeMap((error, i) => {
      const retryAttempt = i + 1;
      // if maximum number of retries have been met
      // or response is a status code we don't wish to retry, throw error
      if (
        retryAttempt > maxRetryAttempts ||
        excludedStatusCodes.find(e => e === error.status)
      ) {
        console.log(error);
        return throwError(error);
      }
      console.log(
        `Attempt ${retryAttempt}: retrying in ${retryAttempt *
        scalingDuration}ms`
      );
      // retry after 1s, 2s, etc...
      return timer(retryAttempt * scalingDuration);
    }),
    finalize(() => console.log('We are done!'))
  );
};

Примечание:

Кто-то предложил около shareReplay(), и я попытался реализовать его, но это былоне может обработать один и тот же запрос, сделанный двумя другими компонентами / источниками.

Следующим должно быть только 6, вместо этого они равны 12 при быстром нажатии двух кнопок, вызывающих один и тот же API (длительность масштабирования составляла 1000 мс).

enter image description here

ПРИМЕЧАНИЕ:

Пожалуйста, избегайте использования FLAGS, по моему мнению, это последняя ядерная бомба.

1 Ответ

1 голос
/ 20 октября 2019

Обратите внимание, что каждый раз, когда вы вызываете getInfoAPI() http.get(), создается новая наблюдаемая, и shareReplay() делится этой новой наблюдаемой, она не объединяет два вызова. Если вы хотите, чтобы вызывающий объект получил объединенную наблюдаемую, вы можете вернуть одну и ту же наблюдаемую из обоих вызовов. Но это неправильное решение, я объясню позже. Например:

export class DataService {
  private readonly getInfoRequest = new Subject<GetInfoRequest>();
  private readonly getInfoResponse = this.getInfoRequest.pipe(
    exhaustMap(request => { 
      const callURL = createGetInfoUrl(request);
      const callParams = createGetInfoParams(request);
      return this.http.get(callURL, callParams).pipe(
        retryWhen( ... );
      );
    })
  );

  public getInfoAPI(category:string, id:string = "", page:string = "1", limit:string = "10") {
    this.getInfoRequest.next({ category: category, id: id, page: page, limit: limit });
    return this.getInfoResponse;
  }

  ...
}

Приведенный выше код делает то же самое, что вы пытаетесь реализовать с помощью shareReplay(), но что если аргументы вызовов не совпадают? Один компонент запросил первую страницу, а другой компонент запросил вторую, второй компонент получит первую страницу вместо второй. Итак, мы должны учитывать параметры вызова, все становится более сложным.

Решение может заключаться в том, чтобы обернуть HttpService с хранилищем, которое будет обрабатывать кэширование, оно может кэшировать данные в памяти, внутри базы данных. или где-то еще, но я сомневаюсь, что это то, что вы хотите. Как я понимаю, проблема заключается в одновременных запросах, лучший способ - предотвратить такой запрос. Например, если запрос вызван нажатием кнопки, просто отключите кнопку во время выполнения запроса или пропустите повторные запросы. Это обычный способ решения таких проблем.

...