我在一个 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 次。
答案 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 }))
)
),
)