retryWhen 只触发一次,而不是多次

时间:2021-02-01 00:58:06

标签: angular rxjs ngrx

我在一个 angular 项目中使用 NGRX 来跟踪一堆文件的上传状态。

我想将活动上传的数量限制为 3。我有一个效果,每当一个新文件被添加到队列时响应一个动作。在效果中,我想判断这个文件是否可以开始上传。

这是我的效果:


canStartUpload$ = createEffect(() => 
    this.actions$.pipe(
        ofType(ImageActions.updateImageWithFullUpload),
        withLatestFrom(this._store.select(selectActiveUploadsTotal)), // gets active uploads from state
        mergeMap((values) => {
            return iif(
                () => values[1] < 3, // conditionally checks if the current upload 
                this.tis.getImageUploadS3Link(values[0].image._id)
                    .pipe(map(({url}) => ImageActions.uploadImage({image: values[0].image, url }))), // Starts upload
                throwError(new Error('Cannot upload at the moment'))// throws errors to be caught by 'retryWhen' method. 
            )

        }),
        retryWhen(errors => {
           return errors.pipe(
               tap(err => console.log(err)),// This only logs once, not multiple times
               tap(() => console.log("Retrying Upload")),
               delay(10000),
               take(10)
           )
        })

    )
)

问题是,源 observable 没有重试多次。它记录“重试上传”一次,然后再也不记录,即使条件检查仍然说它应该重试。

我的猜测是,当在外部流上抛出错误时,流就会终止,而不是重试。

如果是这种情况,我如何创建一个可以重试多次的内部流,同时还从状态机获取最新状态并将该值传递给 iif function.

为了准确解释我要完成的任务,这里有一些伪代码:

动作触发时开始处理效果


从状态机获取活动上传

检查条件是否满足(少于 3 个活动上传)

如果为真 --> 开始上传

如果为 false --> 10 秒后重试(从状态机获取状态开始)


最多尝试此块 10 次。

2 个答案:

答案 0 :(得分:0)

试试这个:

1.) 有一个效果流关注uploadImage或cant upload image的成功动作发射。

2.) 为效果中无法上传图片的动作设置一个监听器,然后做你想做的事情(延迟 10 秒,采取(10)),然后重新发送该动作。

我怀疑的是 take(10) 现在 take(10) 已成为全球性的。这意味着如果用户上传一个文件并重试 5 次,然后他们再次尝试上传并重试 5 次,然后第 3 次重试失败。我认为 take(10) 应该以某种方式刷新。

canStartUpload$ = createEffect(() => 
    this.actions$.pipe(
        ofType(ImageActions.updateImageWithFullUpload),
        withLatestFrom(this._store.select(selectActiveUploadsTotal)), // gets active uploads from state
        mergeMap((values) => {
            return iif(
                () => values[1] < 3, // conditionally checks if the current upload 
                this.tis.getImageUploadS3Link(values[0].image._id)
                    .pipe(map(({url}) => ImageActions.uploadImage({image: values[0].image, url }))), // Starts upload
                map(_ => ImageActions.cantUploadAtTheMoment()), // create this action
            )
        }),
    )
)

cantUploadAtTheMoment$ = createEffect(() => {
   this.actions$.pipe(
     ofType(ImageActions.cantUploadAtTheMoment),
     delay(10000),
     take(10),
     map(_ => ImageActions.updateImageWithFullUpload) // recall the action that will start the canStartUpload$ stream.
   )
});

答案 1 :(得分:0)

解决此问题的一种方法是使用 useEffectsErrorHandler config property

createEffect(
  () => /* ... */,
  { useEffectsErrorHandler: true }
);

通过使用上述选项,defaultEffectsErrorHandler function 将确保如果效果流没有捕获错误,它将被重新订阅。默认情况下,最多尝试 10 次:

export function defaultEffectsErrorHandler<T extends Action>(
  observable$: Observable<T>,
  errorHandler: ErrorHandler,
  retryAttemptLeft: number = MAX_NUMBER_OF_RETRY_ATTEMPTS
): Observable<T> {
  return observable$.pipe(
    catchError((error) => {
      if (errorHandler) errorHandler.handleError(error);
      if (retryAttemptLeft <= 1) {
        return observable$; // last attempt
      }
      // Return observable that produces this particular effect
      return defaultEffectsErrorHandler(
        observable$,
        errorHandler,
        retryAttemptLeft - 1
      );
    })
  );
}

您可以提供自己的effects-error-handler函数:

{
  provide: EFFECTS_ERROR_HANDLER,
  useValue: yourCustomHandler,
},

另一种解决这个问题的方法,不涉及设置任何其他选项,就是稍微调整一下效果:

canStartUpload$ = createEffect(() => 
  this.actions$.pipe(
  ofType(ImageActions.updateImageWithFullUpload),
  
  mergeMap(
    action => timer(0, 10_000).pipe(
      withLatestFrom(this._store.select(selectActiveUploadsTotal)), // gets active uploads from state,
      filter([, activeUploadsTotal] => activeUploadsTotal < 3),
      mapTo(action),

      // stop the timer when the condition is true
      // otherwise, let the timer be active and keep checking
      take(1),
    )
  ),

  mergeMap(
    action => this.tis.getImageUploadS3Link(values[0].image._id).pipe(
      map(({url}) => ImageActions.uploadImage({image: values[0].image, url }))
    )
  ),
)