RxJs——错误处理(二)

百科知识2025-04-261

从前文我们知道,错误处理有两种方式,一种是重新抛出一个错误,另一个是提供一个默认的回调值。

今天我们介绍错误处理的另一些方式,先来看看重试。

重试策略

有一点需要记住,一旦流出现了错误,我们不能恢复它。但是没有什么能阻碍我们订阅其派生类对应的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('/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('/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 错误处理策略的学习。