Angular/RXJS - 跟踪陷入重试模式的 HTTP 请求

标签 angular rxjs observable httprequest

我有一个函数为我处理多个 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)

以下是我的服务与我的重试功能,

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,而不是在快速单击调用相同 API 的两个按钮时为 12(缩放持续时间为 1000 毫秒)。

enter image description here

注意:

请避免使用 FLAGS在我看来,这是最后的核武器。

最佳答案

注意,每次调用getInfoAPI() http.get()创建一个新的 observable 和 shareReplay()共享新的 observable,它不会合并两个调用。如果你想让调用者得到一个合并的 observable,你可以从两个调用中返回相同的 observable。但这是错误的解决方案,我稍后会解释。例如:

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,该存储库将处理缓存,它可以将数据缓存在内存中、数据库中或其他地方,但我怀疑这是否是您想要的。据我了解,问题是同时请求,更好的方法是阻止此类请求。例如,如果请求是由单击按钮触发的,则只需在请求执行时禁用该按钮,或跳过重复的请求。这是解决此类问题的常用方法。

关于Angular/RXJS - 跟踪陷入重试模式的 HTTP 请求,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58419948/

相关文章:

javascript - 在使用 angular + .net Core 开发应用程序时,我应该将资源放在哪里

javascript - Angular 4 - 即使在手动安装 portfinder 后,我仍然无法找到模块 'portfinder'

javascript - 在 Typescript/Angular 中,instanceof 函数参数返回 false

javascript - rxjs 冷 obs 存储所有消息

RxJS:我如何 "manually"更新 Observable?

f# - 澄清 F# 中的事件、观察者和邮箱处理器

angular - 'value' 应该是一个有效的 JavaScript Date 实例

javascript - *ngFor 在给定数组类型的 Observable 时无法读取未定义的属性 'subscribe'

Angular 示意图 : trustedSubscriber. _addParentTeardownLogic

javascript - 随机运动 Angular