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!')),
);
};
