网站搜索

typescript - 如何在 Angular 7.x 中根据 snackbar 动作点击有条件地使用 retryWhen


我正在通过实现 HttpInterceptor 的 ErrorInterceptor 自动开发重试功能。我读到 retryWhen 正是我需要的。我希望它的行为是在点击 Snackbar 的 'RETRY' 按钮时有条件地重试,并且我不希望它无限制地发送请求(所以,也许在 3 次之后就不能再尝试)。问题是,现在我不知道如何在通知 snackbar 的操作按钮被点击后有条件地重试。

我已经尝试过直接触发 notificationService 来显示通知,使用简单的代码:

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request).pipe(
      retryWhen(error => {
        this.notificationService.showNotification('problem RETRY', NotificationActionLabel.Retry, 4000, () => {
          console.log('retrying');
          return of(error);
        });
        return throwError(error);
      }),

这不再重试该功能,它只是停止。

然后我找到了一些关于 retryWhen 的代码,它描述了一个 generalRetryStrategy https://www.learnrxjs.io/operators/error_handling/retrywhen.html。我添加了这个,但我希望它能够有条件地触发(与操作按钮关联)。

我在通知服务上有一个回调函数

export class NotificationService {
  constructor(private snackBar: MatSnackBar) {}

  public showNotification(message: string, action?: string, duration?: number, callBackFunction?: Function) {
    const defaultDuration: number = 2500;
    const notificationRef = this.snackBar.open(message, action, {
      duration: duration == null ? defaultDuration : duration,
      panelClass: ['snackbar-styling'],
    });

    notificationRef.onAction().subscribe(result => callBackFunction(result));
  }
}

拦截器现在如下:

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request).pipe(
      retryWhen(this.genericRetryStrategy()),
      catchError((error: HttpErrorResponse) => {
        let errorMessage = '';

        // Client side error
        if (error.error instanceof ErrorEvent) {
          errorMessage = `Error: ${error.error.message}`;
        } else {
          // Server Side Error
          errorMessage = this.generateErrorMessage(error);
        }

        this.loggingService.logErrorMessage(error.error.message);
        this.notificationService.showNotification(errorMessage, null, 4000);
        return throwError(error.error.message);
      }),
    );

使用以下函数 genericRetryStrategy,请注意,这几乎与 learn-rxjs 中的代码没有任何不同。

 genericRetryStrategy = ({
    maxRetryAttempts = 3,
    scalingDuration = 1000,
    excludedStatusCodes = [],
  }: {
    maxRetryAttempts?: number;
    scalingDuration?: number;
    excludedStatusCodes?: HttpStatusCode[];
  } = {}) => (attempts: Observable<any>) => {
    return attempts.pipe(
      mergeMap((error, i) => {
        this.notificationService.showNotification('attempting', 'retry', 4000, () => {
          const retryAttempt = i++;
          // if maximum number of retries have been met
          // or response is a status code we don't wish the retry, throw error
          if (retryAttempt > maxRetryAttempts || excludedStatusCodes.find(e => e === error.status)) {
            return throwError(error);
          }

          console.log(`Attempt ${retryAttempt}: retrying in ${retryAttempt * scalingDuration}ms`);

          //retry after 1s, 2s, etc...
          return timer(retryAttempt * scalingDuration);
        });
        return throwError(error);
      }),
      finalize(() => console.log('We are done!')),
    );
  };

我预期它只有在通知服务回调函数真正被调用时(即点击“重试”按钮时)才会触发重试功能。现在它会立即跳过通知服务调用并直接返回错误 (throwError(error))。

任何帮助都非常感激。

答案#1

它会跳过它并返回错误,因为据我所见,这正是你在 mergeMap 中所做的,你发送一个通知(带回调),然后 return throwError(error);。

未测试的代码如下。

将通知更改服务设置为返回可观察对象:

export class NotificationService {
  constructor(private snackBar: MatSnackBar) {}

  public showNotification(message: string, action?: string, duration?: number, callBackFunction?: Function) {
    const defaultDuration: number = 2500;
    const notificationRef = this.snackBar.open(message, action, {
      duration: duration == null ? defaultDuration : duration,
      panelClass: ['snackbar-styling'],
    });

    notificationRef.onAction().subscribe(result => callBackFunction(result));
  }
}

然后尝试类似这样的东西(也许可以去掉计时器,目前它会等待显示 snackBar,但就我个人而言,这似乎毫无意义,因为 snackbar 的操作应该直接触发重试 IMO):

let genericRetryStrategy = ({
    maxRetryAttempts = 3,
    scalingDuration = 1000,
    excludedStatusCodes = [],
  }: {
    maxRetryAttempts?: number;
    scalingDuration?: number;
    excludedStatusCodes?: HttpStatusCode[];
  } = {}) => (attempts: Observable<any>) => {
    return attempts.pipe(
      mergeMap((error, i) => {
        const retryAttempt = i + 1;
        if (retryAttempt > maxRetryAttempts || excludedStatusCodes.find(e => e === error.status)) {
          return throwError(error);
        }
        console.log(`Attempt ${retryAttempt}: retrying in ${retryAttempt * scalingDuration}ms`);
        return timer(retryAttempt * scalingDuration).pipe(
          switchMap(this.notificationService.showNotification('attempting', 'retry', 4000))
        );
      }),
      finalize(() => console.log('We are done!')),
    );
  };