摘要
本文将为您解释为什么在实际项目中为什么不要调用 onError
以及尽量不使用 Driver
。同时给出一种合理的解决方案,让我们仍然可以愉快的传递 Error ,并对 Value 进行处理。
忘记 onError
onError
释放资源
可能这个标题有些吼人,不是说 Rx 中的 Error 处理是很好的方案吗?可以把 Error 归到一起处理。笔者在这里也觉得这是一个很好的方案,但有一个地方非常头疼,发射 Error 后是释放对应的事件链,也就是数据流。还是用网络请求举例,比如登录。我们打算做一个登录 -》 成功 -》保存 token -》 用 token 获取用户信息等等。
在登录的部分,点击登录,进行验证,很明显,如果密码有误,
画图,

图片表达的很清晰,对应代码的代码是:
button
.rx_tap // 点击登录
.flatMap(provider.login) // 登录请求
.map(saveToken) // 保存 token
.flatMap(provider.requestInfo) // 获取用户信息
.subscribe(handleResult) // 处理结果
代码和流程图是一个样子的,效果还不错。
运行一下,输入正确的账号密码,登录,登录成功,获取用户信息。一切正常。
但是我们来看下面这种场景,登录失败(不论是因为网络错误,还是因为密码错误之类的原因,我们都对这些错误调用了 onError
传递错误信息),直接将 error 传递到事件结尾,即显示登录错误的信息。此时再去点击登录就不会有任何提示了。
因为上面这一条点击登录事件链都被 dispose 了。
这是一个 bug 。我们不希望在第一次点击登录失败后,再次点击登录缺没什么反应。
事实上在 Try! Swift 大会上有一场 POP 的分享,Demo 地址 RxPagination 。试着把网络关了,拉取一下数据,再打开网络,再拉取一下数据看看?此时是没有什么反应的。补一句,这个项目是值得学习一下的。
用官方的方法处理 Error ?
在讨论用官方的方式处理 Error 前,我们先来确认一件事情,处理一个登录流程,如果出现了错误是否应该继续下去,答案是显然的,不继续,停止本次事件。
官方给出了一下几种操作:
retry
catchError
catchErrorJustReturn
doOnError
很可惜,前三种方法都是处理 error ,将 error 变换成正常的值继续下去,并没有停止本次事件。而 doOnError
只是在出现 error 的时候做点什么事情,并不能对事件流有什么影响。
使用 Result
enum Result<T> {
case value(T)
case error(ErrorProtocol)
}
Swift 中的枚举关联值是如此的强大,这可以帮我们解决 Error 的处理,如果 case 为 error ,那就不处理,将 error 传递下去即可。
相比原有的 onError
有如下优势:
- 不因为 error 释放资源
- 方便对 error 传递、处理
类似这样:
provider.request(GitHubAPI.Authorize)
.map { result -> Result<String> in
switch result {
case .value(let value):
return Result.value(value["token"].stringValue)
case .error(let error):
return Result.error(error)
}
}
.flatMap { result -> Observable<Result<JSON>> in
switch result {
case .value(let token):
return provider.request(GitHubAPI.AccessToken(code: token))
case .error(let error):
return Observable.just(Result.error(error))
}
}
.subscribeNext { json in
// ...
}
catch 等系列方法也可以直接在这里替代,而且更灵活了一些,可以返回任何我们想要的类型。
过多的“无用”代码
比如我们要进行多个操作,在第一个或第二个操作就可能出现 error 时,我们的代码会变得很臃肿,也就是有很多的 case .error(let error):
的代码。
这并不优雅。但笔者会在下一篇使用函数式优雅的解决该问题,同时探讨如何在 FRP 中应用 Functional 进一步提高代码的可读性和复用性。