RxSwift学习之旅 - 使用Result传递Error

前言

我们知道一个可被观察序列在它们收到error或者completed事件的时候会终止,终止也就意味着它的订阅者不会收到任何新的消息了。当我们开始学习Rx的时候可能还不会意识到这条规则的后果。

example

很多的应用通常会在点击某个按钮的时候发网络请求,下面来看看这个例子。

image

点击Success模拟调用一个api请求,返回成功,同样,点击Failure触发error,点击会增加计数。

code

首先把成功的点击返回true,失败的点击返回false,然后合并成单个序列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let successCount = Observable
.of(success.rx.tap.map { true }, failure.rx.tap.map { false })
.merge()
.flatMap {
[unowned self] performWithSuccess in
return self.performAPICall(shouldEndWithSuccess: performWithSuccess)
}.scan(0) { accumulator, _ in
return accumulator + 1
}.map { "\($0)" }

successCount.bindTo(self.successCount.rx.text)
.disposed(by: disposeBag)

successCount.subscribe(
onDisposed:{
print("dispose")
}
).disposed(by: disposeBag)

private func performAPICall(shouldEndWithSuccess: Bool) -> Observable<Void> {
if shouldEndWithSuccess {
return .just(())
} else {
return .error(SampleError())
}

当点击Success的时候会增加成功次数,但是当你点击Failure的时候,整个可被观察序列会被释放,之后不管你怎么点Success都不会再增加成功次数。

performAPICall返回了一个错误的事件的时候,其实和你在发送网络请求的时候也会出现。所以使用flatMap也会把内部的nexterror事件传到主序列。

结果,主序列收到error事件,就终结了。。。

如何处理?

上面的情况假如Success按钮是登录按钮,那么在错误后就不能点击了,这不是我们想要的。

这里我们可以借助Result来传递错误信息。

其实在上一篇Moya里面很多地方都有使用到Result来传递错误。

创建一个Result:

1
2
3
4
enum Result<T>{
case success(T)
case failure(Swift.Error)
}

修改performAPICall返回Result:

1
2
3
4
5
6
7
private func performAPICall2(shouldEndWithSuccess: Bool) -> Observable<Result<Void>> {
if shouldEndWithSuccess {
return .just(Result.success())
} else {
return .just(Result.failure(SampleError()))
}
}

然后分别处理成功和失败的情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
successCount
.flatMap{
$0.filterValue()
}
.scan(0) { accumulator, _ in
return accumulator + 1
}
.map { "\($0)" }
.bindTo(self.successCount.rx.text)
.disposed(by: disposeBag)

successCount
.flatMap{
$0.filterError()
}
.scan(0) { accumulator, _ in
return accumulator + 1
}
.map { "\($0)" }
.bindTo(self.failureCount.rx.text)
.disposed(by: disposeBag)

这里增加了filterValuefilterError来获取我们想要的值。

如果不关心原来的错误,只处理成功,也可以增加如下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
func map<T>(transform: (Value) throws -> T) -> Result<T> {
switch self {
case .Success(let object):
do {
let nextObject = try transform(object)
return Result<T>.Success(nextObject)
} catch {
return Result<T>.Failure(error)
}
case .Failure(let error):
return Result<T>.Failure(error)
}
}

错误接着往下传,执行逻辑,成功返回,发生错误传递错误。

使用RxSwiftExt

除了上面的方式,也可以使用RxSwiftExt提供的materialize操作。

它会把Observable<T>intoObservable<Event<T>> , 通过下面两个方法分别获取值和错误:

  • elements() which returns Observable
  • errors() which returns Observable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
let result = Observable
.of(success.rx.tap.map { true }, failure.rx.tap.map { false })
.merge()
.flatMap { [unowned self] performWithSuccess in
return self.performAPICall(shouldEndWithSuccess: performWithSuccess)
.materialize()
}.share() //share

result.elements()
.scan(0) { accumulator, _ in
return accumulator + 1
}.map { "\($0)" }
.bindTo(successCount.rx.text)
.disposed(by: disposeBag)

result.errors()
.scan(0) { accumulator, _ in
return accumulator + 1
}.map { "\($0)" }
.bindTo(failureCount.rx.text)
.disposed(by: disposeBag)

result.subscribe(
onDisposed:{
print("dispose")
}
).disposed(by: disposeBag)

和上次一样的把元素和错误一起包裹了一下,往下传递。

本文相关代码:

RxSwiftResult

AloneMonkey wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!