RxSwift - 为什么存在 catchError

RxSwift 有着优秀的处理错误的方案,但在讨论这个问题之前,我们不如先来思考下为什么在 RxSwift 中有 catchError 操作符。

我们先来想象几个场景:

  • 在一个页面需要相机权限才能正常工作,但用户禁止了该权限;
  • 在一个页面因为某种因素,请求数据失败了;
  • 在一个提示仅限尝试输入 5 次的支付页面用户输入了 5 次密码都失败了。

这些都属于出现错误的场景。

但在讨论上述问题前,我们先来思考一下在 RxSwift 中,出现 error 会发生什么事情。

来看下面这段代码:

Observable<Int>.from([1, 2, 3, -1, 5])
    .map { value -> Int in
        if value > 0 {
            return value
        } else {
            throw CustomError.myCustom
        }
    }
    .debug()
    .subscribe()
    .disposed(by: disposeBag)

输出结果为:

-> subscribed
-> Event next(1)
-> Event next(2)
-> Event next(3)
-> Event error(myCustom)
-> isDisposed

我们可以清晰地看到在发射时到变换再到订阅的过程中,我们发射了一个 -1 ,在 map 过程中抛出了错误,导致我们收到 1 、 2 、 3 后只收到了一个 error myCustom

但这个场景一般不适合直接应用到开发中。举个例子,当我们点击 Button 时,进行一个网络请求,网络请求出现错误,抛出 error ,展示错误信息。整个事件结束了,当我们再次点击该 Button 时,则不会再触发网络请求。 类似上述代码,5 没有被订阅者接收到。

当 Observer 收到 Observable 一个错误时,说明该 Observable 不能正常传递值了, Observer 不再对该 Observable 感兴趣,此时 Observer 取消订阅了该 Observable 。Observable 再去传递任何值 Observer 也不知道了。

为此,我们需要 catch 这个网络请求的错误,保证一切在正常的运行。而 catch 后做什么也是一件重要的事情,比如我们可以选择重试、用一个默认值替换或者阻止本次事件继续向下发射。

我们来 catch 上述发射数字的代码:

Observable<Int>.from([1, 2, 3, -1, 5])
    .map { value -> Int in
        if value > 0 {
            return value
        } else {
            throw CustomError.myCustom
        }
    }
    .catchError { (error) -> Observable<Int> in
        return Observable.just(1)
    }
    .debug()
    .subscribe()
    .addDisposableTo(disposeBag)

结果如下:

-> subscribed
-> Event next(1)
-> Event next(2)
-> Event next(3)
-> Event next(1)
-> Event completed
-> isDisposed

Observer 没有收到 error ,在前面触发 error 时,我们通过调用方法 catchError 将出现错误的 Observable 替换成了一个新的 Observable ,这里是 Observable.just(1)

这可能并不是我们想要的,如果我们还想接收到这个 5 怎么办?

我们需要先明确一个概念:

除非通过重新订阅,否则在一个 Observable 发射 error 后,你不再能接收到任何它发射的任何值。

上述代码中:

Observable<Int>.from([1, 2, 3, -1, 5])
    .map { value -> Int in
        if value > 0 {
            return value
        } else {
            throw CustomError.myCustom
        }
    }

这个Observable已经发射了CustomError.myCustom,我们无论如何也收不到5这个值。

但稍加修改,我们就可以收到5这个值:

Observable<Int>.from([1, 2, 3, -1, 5])
    .flatMap { value -> Observable<Int> in
        Observable.just(value)
            .map { value -> Int in
                if value > 0 {
                    return value
                } else {
                    throw CustomError.myCustom
                }
            }
            .catchErrorJustReturn(1)
    }
    .debug()
    .subscribe()
    .addDisposableTo(disposeBag)

输出结果:

-> subscribed
-> Event next(1)
-> Event next(2)
-> Event next(3)
-> Event next(1)
-> Event next(5)
-> Event completed
-> isDisposed

原因在于我们 catch 的 Observable 是:

Observable.just(value)
    .map { value -> Int in
        if value > 0 {
            return value
        } else {
            throw CustomError.myCustom
        }
    }

当这一 Observabl e出现错误时,替换Observable.just(1)

这样一来,在 flatMap 后的 Observable 变成了正常的 Observable ,我们可以继续变换值,自然最终我们可以接收到5。

我们还可以通过返回一个 Observable.empty() 忽略出现错误的值:

Observable<Int>.from([1, 2, 3, -1, 5])
    .flatMap { value -> Observable<Int> in
        Observable.just(value)
            .map { value -> Int in
                if value > 0 {
                    return value
                } else {
                    throw CustomError.myCustom
                }
            }
            .catchError { (error) -> Observable<Int> in
                return Observable.empty()
        }
    }
    .debug()
    .subscribe()
    .addDisposableTo(disposeBag)

此时输出结果为:

-> subscribed
-> Event next(1)
-> Event next(2)
-> Event next(3)
-> Event next(5)
-> Event complete

切记:在使用 catchError 时,我们要时刻明确我们 catch 的是哪个 Observable ,并将这个 Observable 替换成了什么 Observable 。

接下来我们将讨论如何创建一些定制的错误处理方法,以及如何在实际项目中处理错误的时机与方案。