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

我们先来想象几个场景:

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

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

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

来看下面这段代码:

1
2
3
4
5
6
7
8
9
10
11
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)

输出结果为:

1
2
3
4
5
6
-> 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 上述发射数字的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)

结果如下:

1
2
3
4
5
6
7
-> 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 后,你不再能接收到任何它发射的任何值。

上述代码中:

1
2
3
4
5
6
7
8
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这个值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)

输出结果:

1
2
3
4
5
6
7
8
-> subscribed
-> Event next(1)
-> Event next(2)
-> Event next(3)
-> Event next(1)
-> Event next(5)
-> Event completed
-> isDisposed

原因在于我们 catch 的 Observable 是:

1
2
3
4
5
6
7
8
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() 忽略出现错误的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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)

此时输出结果为:

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

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

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