NGRX:http 重试拦截器导致失败操作未被触发
NGRX: http retry interceptor causing failure action not to be fired
我正在使用 NGRX,并且我希望对 API 进行简单的 GET 请求 重试 五次。原因是我使用的是 Azure Cosmos-DB,有时我会受到限制。 (免费套餐)。
我为此创建了一个 http 拦截器,它非常简单,看起来像那样
@Injectable()
export class HttpRetryInterceptor implements HttpInterceptor {
public intercept(
request: HttpRequest<any>,
httpHandler: HttpHandler
): Observable<HttpEvent<any>> {
const nextRequest = request.clone();
// here the NGRX failure action is not being triggered after five failing requests
return httpHandler.handle(nextRequest).pipe(
retryWhen(errors => errors.pipe(delay(1000), take(5))),
last()
);
}
}
这工作得很好,每个失败的 http 请求都会重试五次,延迟 1000 毫秒。
现在的问题是,当请求实际失败五次时,效果中的失败操作并未被触发。
load$ = createEffect(() =>
this.actions$.pipe(
ofType(globalStatsActions.load),
mergeMap(() =>
this.globalStatsService.load().pipe(
map(stats =>
globalStatsActions.loaded({
latestStats: stats
})
),
catchError(() => of(globalStatsActions.loadFailed())) // not being called using the http-interceptor
)
)
)
);
奇怪的是,当 http 拦截器使用运算符 retry
而不是 retryWhen
时,它工作得很好。可悲的是,使用该运算符您无法定义 delay
,这在我的情况下是必需的。
另一个有趣的事实是,当在效果中使用的服务上使用完全相同的重试逻辑时,它工作得很好。
// here the failure action is being triggered after five failing requests
public load(): Observable<GlobalStats> {
return this.http.get<GlobalStats>(`${this.baseUrl}stats`)
.pipe(
retryWhen(errors => errors.pipe(delay(1000), take(5))),
last()
);
虽然代码不多,可以复制到我正在使用的每个 http 服务上,但我更愿意为它使用拦截器。
我似乎找不到原因,我希望那里的任何人都知道为什么这不起作用。
Another interesting fact is, that when using the very same retry logic on the service used in the effect, it works just fine.
当达到 n
次尝试时,retry(n)
将简单地传递错误通知。当尝试 < n
时,它将取消订阅源,然后它将 重新订阅 。
retryWhen(fn)
维护一个内部订阅,这是函数fn
提供的可观察结果。 fn
的单个参数是一个 错误主题 ,它会在每次发生错误时推送值。
所以,如果你有
retryWhen(subjectErrors => subjectErrors.pipe(a(), b()))
本质上等同于:
const subjectErrors = new Subject();
subjectErrors.pipe(a(), b()).subscriber(innerSubscriber);
retryWhen
内部也使用了一个innerSubscriber
,主要作用是在有值(next notification
)到达时通知
发生这种情况时,源将重新订阅。在这种情况下,source 是一个发出 http 请求的 observable,当发生错误时,它会发送一个 error
通知;如果请求成功,它将发出 next
通知,然后是 complete
通知。
take(5)
来自 retryWhen(errors => errors.pipe(delay(1000), take(5)))
意味着当达到 5 次尝试时,innerSubscriber
(上面那个)将发出 complete
通知。
那么你有 last
,它 没有默认值 。这意味着当它收到 complete
通知时,之前没有收到任何 next
通知,它将发出错误(默认行为)。
然后错误被catchError()
捕获在你的效果中。所以这应该可以解释为什么这种方法有效。
What is weird is, that when the http-interceptor uses the operator retry rather than retryWhen it works just fine.
这是我无法解释的原因,因为使用上一节中的信息,它应该可以正常工作,创建的流应该是相同的。
如果你有
load () {
return this.http.get(...);
}
并假设拦截器是您唯一使用的拦截器,在
mergeMap(() =>
this.globalStatsService.load().pipe(
map(stats =>
globalStatsActions.loaded({
latestStats: stats
})
),
catchError(() => of(globalStatsActions.loadFailed())) // not being called using the http-interceptor
)
)
this.globalStatsService.load()
应与
相同
of(request).pipe(
concatMap(
req => new Observable(s => { /* making the request.. */ }).pipe(
retryWhen(errors => errors.pipe(delay(1000), take(5))),
last(),
)
)
)
这应该与上一节中发生的事情相同。
所以,如果我没有遗漏任何东西,应该达到 catchError
。
如果不是,则可能意味着您有其他 catchError
或 last
已收到一个值,因此不会引发错误。
在这种情况下它会记录一些东西吗?
// here the NGRX failure action is not being triggered after five failing requests
return httpHandler.handle(nextRequest).pipe(
retryWhen(errors => errors.pipe(delay(1000), take(5))),
tap(console.log),
last()
);
编辑
正如在 source code 中所见,发生请求的可观察对象将通过其订阅者发送一个事件,指示请求已被分派。
考虑到这一点,您可以使用 filter
运算符:
return httpHandler.handle(nextRequest).pipe(
filter(ev => ev.type !== 0),
retryWhen(errors => errors.pipe(delay(1000), take(5))),
last()
);
我正在使用 NGRX,并且我希望对 API 进行简单的 GET 请求 重试 五次。原因是我使用的是 Azure Cosmos-DB,有时我会受到限制。 (免费套餐)。
我为此创建了一个 http 拦截器,它非常简单,看起来像那样
@Injectable()
export class HttpRetryInterceptor implements HttpInterceptor {
public intercept(
request: HttpRequest<any>,
httpHandler: HttpHandler
): Observable<HttpEvent<any>> {
const nextRequest = request.clone();
// here the NGRX failure action is not being triggered after five failing requests
return httpHandler.handle(nextRequest).pipe(
retryWhen(errors => errors.pipe(delay(1000), take(5))),
last()
);
}
}
这工作得很好,每个失败的 http 请求都会重试五次,延迟 1000 毫秒。
现在的问题是,当请求实际失败五次时,效果中的失败操作并未被触发。
load$ = createEffect(() =>
this.actions$.pipe(
ofType(globalStatsActions.load),
mergeMap(() =>
this.globalStatsService.load().pipe(
map(stats =>
globalStatsActions.loaded({
latestStats: stats
})
),
catchError(() => of(globalStatsActions.loadFailed())) // not being called using the http-interceptor
)
)
)
);
奇怪的是,当 http 拦截器使用运算符 retry
而不是 retryWhen
时,它工作得很好。可悲的是,使用该运算符您无法定义 delay
,这在我的情况下是必需的。
另一个有趣的事实是,当在效果中使用的服务上使用完全相同的重试逻辑时,它工作得很好。
// here the failure action is being triggered after five failing requests
public load(): Observable<GlobalStats> {
return this.http.get<GlobalStats>(`${this.baseUrl}stats`)
.pipe(
retryWhen(errors => errors.pipe(delay(1000), take(5))),
last()
);
虽然代码不多,可以复制到我正在使用的每个 http 服务上,但我更愿意为它使用拦截器。
我似乎找不到原因,我希望那里的任何人都知道为什么这不起作用。
当达到Another interesting fact is, that when using the very same retry logic on the service used in the effect, it works just fine.
n
次尝试时,retry(n)
将简单地传递错误通知。当尝试 < n
时,它将取消订阅源,然后它将 重新订阅 。
retryWhen(fn)
维护一个内部订阅,这是函数fn
提供的可观察结果。 fn
的单个参数是一个 错误主题 ,它会在每次发生错误时推送值。
所以,如果你有
retryWhen(subjectErrors => subjectErrors.pipe(a(), b()))
本质上等同于:
const subjectErrors = new Subject();
subjectErrors.pipe(a(), b()).subscriber(innerSubscriber);
retryWhen
内部也使用了一个innerSubscriber
,主要作用是在有值(next notification
)到达时通知
发生这种情况时,源将重新订阅。在这种情况下,source 是一个发出 http 请求的 observable,当发生错误时,它会发送一个 error
通知;如果请求成功,它将发出 next
通知,然后是 complete
通知。
take(5)
来自 retryWhen(errors => errors.pipe(delay(1000), take(5)))
意味着当达到 5 次尝试时,innerSubscriber
(上面那个)将发出 complete
通知。
那么你有 last
,它 没有默认值 。这意味着当它收到 complete
通知时,之前没有收到任何 next
通知,它将发出错误(默认行为)。
然后错误被catchError()
捕获在你的效果中。所以这应该可以解释为什么这种方法有效。
What is weird is, that when the http-interceptor uses the operator retry rather than retryWhen it works just fine.
这是我无法解释的原因,因为使用上一节中的信息,它应该可以正常工作,创建的流应该是相同的。
如果你有
load () {
return this.http.get(...);
}
并假设拦截器是您唯一使用的拦截器,在
mergeMap(() =>
this.globalStatsService.load().pipe(
map(stats =>
globalStatsActions.loaded({
latestStats: stats
})
),
catchError(() => of(globalStatsActions.loadFailed())) // not being called using the http-interceptor
)
)
this.globalStatsService.load()
应与
of(request).pipe(
concatMap(
req => new Observable(s => { /* making the request.. */ }).pipe(
retryWhen(errors => errors.pipe(delay(1000), take(5))),
last(),
)
)
)
这应该与上一节中发生的事情相同。
所以,如果我没有遗漏任何东西,应该达到 catchError
。
如果不是,则可能意味着您有其他 catchError
或 last
已收到一个值,因此不会引发错误。
在这种情况下它会记录一些东西吗?
// here the NGRX failure action is not being triggered after five failing requests
return httpHandler.handle(nextRequest).pipe(
retryWhen(errors => errors.pipe(delay(1000), take(5))),
tap(console.log),
last()
);
编辑
正如在 source code 中所见,发生请求的可观察对象将通过其订阅者发送一个事件,指示请求已被分派。
考虑到这一点,您可以使用 filter
运算符:
return httpHandler.handle(nextRequest).pipe(
filter(ev => ev.type !== 0),
retryWhen(errors => errors.pipe(delay(1000), take(5))),
last()
);