从前文我们知道,错误处理有两种方式,一种是重新抛出一个错误,另一个是提供一个默认的回调值。
今天我们介绍错误处理的另一些方式,先来看看重试。
重试策略
有一点需要记住,一旦流出现了错误,我们不能恢复它。但是没有什么能阻碍我们订阅其派生类对应的Observable,并且创建一个新的流。
这种策略的工作原理是:
1、我们将获取输入Observable并且订阅它,这将创建一个新的流。
2、如果流没有出错,我们将在输出中展现它的值。
3、如果流出了错误,我们将再次订阅输入Observable,并创建一个全新的流。
重试的时机
这里有一个问题是,我们什么时候要再次订阅输入 Observable,并重试执行输入流?
1、我们要立即重试吗?
2、我们要等待一小段延迟,希望问题得到解决,然后再试一次吗?
3、我们是否只重试有限的次数,然后输出流出错?
其实有心得小伙伴也许已经看出来了,这些问题对应着不同的策略,我们慢慢分析。
为了回答这些问题,我们需要第二个辅助 Observable,我们将其称为通知Observable。通知Observable 将确定何时发生重试尝试。通知Observable 将被 retryWhen 运算符使用,它是重试策略的核心。
RetryWhen弹珠图
请注意,正在重试的 Observable 是从上数第二行中的 1-2 Observable,而不是第一行中的 Observable。第一行带有 r-r 值的 Observable 是 通知Observable,它将确定何时应该发生重试尝试。
我们来分解一下此图的内容
1、Observable 1-2 被订阅,其值立即反映在 retryWhen 返回的输出Observable中
2、即使 Observable 1-2 完成后,仍然可以重试
3、通知Observable 在 Observable 1-2 完成之后发出一个值 r
4、通知Observable 发出的值(在本例中为 r)可以是任何值
5、重要的是值 r 发出的那一刻,因为那将触发 1-2 Observable 被重试
6、Observable 1-2 再次被 retryWhen 订阅,其值再次反映在 retryWhen 的输出 Observable 中
7、然后通知Observable 将再次发出另一个 r 值,并且发生同样的事情:新订阅的 1-2 流的值将开始反映在 retryWhen 的输出中
8、但随后,通知Observable 最终完成
9、此时,1-2 Observable 正在进行的重试尝试也提前完成,这意味着只有值 1 被发出,而不是 2
正如我们所见,retryWhen 只是在每次通知Observable 发出一个值时重试输入 Observable!
这里有一个疑问,为啥只有1发出,而2没有发出?
现在我们了解了 retryWhen 的工作原理,让我们看看如何创建 通知Observable。
创建通知Observable
我们需要直接在传递给 retryWhen 操作符的函数中创建通知Observable。此函数将 Errors Observable 作为输入参数,该参数将输入 Observable 的错误作为值发出。因此,通过订阅这个 Errors Observable,我们可以准确地知道错误发生的时间。现在让我们看看如何使用 Errors Observable 实现立即重试策略。
立即重试策略
为了在错误发生后立即重试失败的 observable,我们所要做的就是返回 Errors Observable 而不做任何进一步的更改。在这种情况下,我们只是将 tap 操作符用于记录目的,因此 Errors Observable 保持不变:
const http$ = this.http.get<Course[]>('/api/courses');
http$.pipe(
tap(() => console.log("HTTP request executed")),
map(res => Object.values(res["payload"]) ),
shareReplay(),
retryWhen(errors => {
return errors
.pipe(
tap(() => console.log('retrying...'))
);
} )
)
.subscribe(
res => console.log('HTTP response', res),
err => console.log('HTTP Error', err),
() => console.log('HTTP request completed.')
);
让我们记住,我们从 retryWhen 函数调用返回的 Observable 是通知Observable!它发出的值并不重要,只有在发出值时才重要,因为这将触发重试尝试。
日志信息
如我们所见,HTTP 请求最初失败,但随后尝试重试,第二次请求成功通过。现在让我们通过检查网络日志来看看两次尝试之间的延迟:
正如我们所看到的,第二次尝试是在错误发生后立即发出的,正如预期的那样。
延迟重试策略
让我们实现一个替代的错误恢复策略,例如在错误发生后等待 2 秒,然后再重试。此策略对于尝试从某些错误中恢复很有用,例如由高服务器流量导致的网络请求失败。在错误是间歇性的情况下,我们可以简单地在短暂的延迟后重试相同的请求,并且请求可能会通过第二次而没有任何问题。
定时Observable创建函数
为了实现延迟重试策略,我们需要创建一个通知Observable,它的值会在每次错误发生后两秒发出。然后让我们尝试使用计时器创建函数创建一个通知Observable。这个计时器函数将接受几个参数:
1、初始延迟,在此之前不会发出任何值
2、一个周期性间隔,以便我们想要周期性地发出新值。
然后让我们看一下定时器功能的弹珠图:
正如我们所看到的,第一个值 0 只会在 3 秒后发出,然后我们每秒都有一个新值。请注意,第二个参数是可选的,这意味着如果我们忽略它,我们的 Observable 将在 3 秒后仅发出一个值 (0),然后完成。这个 Observable 看起来像是一个能够延迟重试尝试的良好开端,所以让我们看看如何将它与 retryWhen 和 delayWhen 运算符结合起来。
delayWhen操作符
需要记住的一点是retryWhen 操作符,这个定义通知Observable的函数只会调用一次。所以我们只有一次机会定义我们的通知Observable,它发出重试尝试的信号。我们将通过获取 Errors Observable 并将其应用 delayWhen 运算符来定义通知Observable。想象一下,在这个弹珠图中,源 Observable a-b-c 是 Errors Observable,它随着时间的推移发出失败的 HTTP 错误:
让我们按照图示,了解 delayWhen 运算符的工作原理:
1、输入 Errors Observable 中的每个值都会被延迟,然后才会显示在输出 Observable 中
2、每个值的延迟可以不同,并且将以完全灵活的方式创建
3、为了确定延迟,我们将根据输入 Errors Observable 的每个值调用传递给 delayWhen 的函数(称为持续时间选择器函数)
4、该函数将发出一个 Observable 来确定每个输入值的延迟何时结束
5、每个值 a-b-c 都有自己的持续时间选择器 Observable,最终会发出一个值(可以是任何值)然后完成
6、当这些持续时间选择器 Observables 中的每一个发出值时,相应的输入值 a-b-c 将显示在 delayWhen 的输出中
7、请注意,值 b 出现在值 c 之后的输出中,这是正常的
8、这是因为 b 持续时间选择器 Observable(从顶部开始的第三条水平线)仅在 c 的持续时间选择器 Observable 之后发出它的值,这就解释了为什么 c 在 b 之前出现在输出中
延迟重试策略实现
现在让我们把所有这些放在一起,看看我们如何在每个错误发生 2 秒后连续重试失败的 HTTP 请求:
onst http$ = this.http.get<Course[]>('/api/courses');
http$.pipe(
tap(() => console.log("HTTP request executed")),
map(res => Object.values(res["payload"]) ),
shareReplay(),
retryWhen(errors => {
return errors
.pipe(
delayWhen(() => timer(2000)),
tap(() => console.log('retrying...'))
);
} )
)
.subscribe(
res => console.log('HTTP response', res),
err => console.log('HTTP Error', err),
() => console.log('HTTP request completed.')
);
让我们分解这里发生的事情:
1、让我们记住传递给 retryWhen 的函数只会被调用一次
2、我们在该函数中返回一个 Observable,它会在需要重试时发出值
3、每次出现错误时,delayWhen 操作符都会通过调用 timer 函数来创建一个持续时间选择器 Observable
4、这个持续时间选择器 Observable 将在 2 秒后发出值 0,然后完成
5、一旦发生这种情况,delayWhen Observable 就会知道给定输入错误的延迟已经过去
6、只有在延迟过去后(错误发生后 2 秒),错误才会显示在通知 Observable 的输出中
7、一旦在通知 Observable 中发出了一个值,retryWhen 运算符将且仅在那时执行重试尝试
日志信息
现在让我们看看这在控制台中是什么样子的!这是一个重试 5 次的 HTTP 请求示例,因为前 4 次出错:
这是相同重试序列的网络日志:
正如我们所见,重试只发生在错误发生后 2 秒,正如预期的那样!
至此,我们已经完成了一些最常用的 RxJs 错误处理策略的学习。