本文讲的是[译] RxJava 中的错误处理,

  • 原文地址:Error handling in RxJava
  • 原文作者:Dmitry Ryadnenko
  • 译文出自:掘金翻译计划
  • 本文永久链接:github.com/xitu/gold-m…
  • 译者:星辰
  • 校对者:张拭心 Liz

Drawing

一旦你开始使用 RxJava 函数库写代码,你会发现一些东西能有很多不同的实现方式,但是有时你很难立即确定哪种方法最好,错误处理就是其中之一。

那么,在 RxJava 中处理错误的最佳方法是什么,又有哪些选择呢?

在 onError 消费者中处理错误

假设你有一个 Observable 可能会产生异常。如何处理呢?第一反应应该是直接在 onError消费者中处理错误。

userProvider.getUsers().subscribe({ users -> onGetUsersSuccess(users) },{ e -> onGetUsersFail(e) } // 停止执行,显示错误信息等。
)

它类似于我们以前使用的 AsyncTasks,并且看起来很像一个 try-catch 块。

这儿有一个大问题。 假设在 userProvider.getUsers() Observable 中存在编程错误,导致NullPointerException 或类似的异常。如果能够立刻崩溃的话就好了,我们可以现场检测出问题并且解决。然而上面的代码中我们无法看到崩溃,因为错误被 onError 处理了,它只会显示一个错误信息或者其他结果。

更糟糕的是,测试时不会有任何崩溃。测试会失败,并伴随着神秘且意想不到的行为。你不得不花时间调试,而不是立即在一个直观/具体的栈中找到原因。

预期的和非预期的异常

首先声明,解释下我所谓的预期中的和非预期中的异常。

可预期异常不是说代码出 bug,而是指运行环境有问题。比如各种 IO 异常,无网络异常等。你的软件应该适当的对这些异常产生反应,或者显示错误消息等。预期的异常类似于第二个有效的返回值,它们是方法签名的一部分。

非预期的异常大多是编程错误。它们可以并且将会在开发的时候出现,但是它们永远不应该发生在生产环境中。至少这是一个目标。但是如果它们确实发生了,通常立即使应用崩溃是一个好主意。这有助于提高问题的关注度然后尽快修复之。

在 Java 中,预期中的异常大多是使用受检异常(直接从 Exception 类子类化)实现的。而大多数预期之外的异常则是使用从 RuntimeException 类派生的未受检异常实现的。

运行时崩溃异常

所以,如果我们想要崩溃,为什么不检查异常是否是一个 RuntimeException,并在 onError消费者内重新抛出它呢?如果不仅仅像之前的例子那样处理它呢?

userProvider.getUsers().subscribe({ users -> onGetUsersSuccess(users) },{ e ->if (e is RuntimeException) {throw e} else {onGetUsersFail(e)}}
)

这可能看起来不错,但它有一些缺陷:

  1. 在 RxJava 2 中,非常令人费解的是它会在实时运行的应用中崩溃,而在测试中不会。在 RxJava 1 中,则无论实时运行还是测试都会崩溃。
  2. 我们想要崩溃的,除了 RuntimeException 之外还有更多未受检异常,这包括 Error等。很难追踪所有的这类异常。

但主要缺点是这样的:

在应用开发过程中,你的 Rx 链将会变得越来越复杂。你的 Observable 也将会在不同的地方被重用,包括你从没料到会使用到的上下文中。

假设你已经决定在这个链中使用 userProvider.getUsers() 这个 Observable:

Observable.concat(userProvider.getUsers(), userProvider.getUsers()).onErrorResumeNext(just(emptyList())).subscribe { println(it) }

当两个 userProvider.getUsers() 都触发一个错误将会发生什么?

现在,你可能认为这两个错误都分别映射到一个空列表上,因此将会有两个空列表被触发。不过你可能会惊讶的发现,实际上只有一个列表被触发。这是因为第一个userProvider.getUsers() 中发生的错误将会终止整个链的上游, concat 的第二个参数永远不会被执行。

你看,RxJava 中的错误是非常具有破坏性的。它们被设计成致命的信号来终止整条链的上游。它们不应该是你的 Observable 接口的一部分。它们表现为意料之外的错误。

Observable 被设计成使用有效输出来表示错误的触发,这限制了它的使用范围。复杂的链在错误的情况下如何工作很不明朗,所以很容易误用这种 Observable 。这最终会导致错误。非常恶心的错误,只能偶尔重现的(特殊情况下,比如缺少网络)而且不会留下堆栈痕迹的错误。

结果类

那么,如何设计 Observable 来让其返回预期的错误呢?只需让它们返回一些 Result 类,即包含操作的结果也包含异常,就像这样:

data class Result<out T>(val data: T?,val error: Throwable?
)

将所有预期的异常包含进去,然后将所有不可预期的都放行而使程序崩溃。避免使用onError 消费者,让 RxJava 为你控制崩溃。

现在,虽然这种途径看起来不是特别优雅或直观,并且产生了相当多的样板,但是我发现它会导致最少的问题。此外,它看起来像是在 RxJava 中进行错误处理的『官方』方式。我看到过它在互联网的多个讨论中被 RxJava 的维护者所推荐。

一些有用的代码段

为了使你的 Retrofit Observable 返回 Result 类,你可以使用这个方便的扩展功能:

fun <T> Observable<T>.retrofitResponseToResult(): Observable<Result<T>> {return this.map { it.asResult() }.onErrorReturn {if (it is HttpException || it is IOException) {return@onErrorReturn it.asErrorResult<T>()} else {throw it}}
}fun <T> T.asResult(): Result<T> {return Result(data = this, error = null)
}fun <T> Throwable.asErrorResult(): Result<T> {return Result(data = null, error = this)
}

这样,你的 Observable userProvider.getUsers() 看起来可以像这样:

class UserProvider {fun getUsers(): Observable<Result<List<String>>> {return myRetrofitApi.getUsers().retrofitResponseToResult()}
}





原文发布时间为:2017年8月30日

本文来自云栖社区合作伙伴掘金,了解相关信息可以关注掘金网站。

[译] RxJava 中的错误处理相关推荐

  1. 交替传译中出现错误怎么办

    我们知道,交替传译(Consecutive Interpretation)是一个涉及听﹑理解﹑记忆﹑笔记﹑整理﹑输出等过程的复杂活动,要完成高质量的交替传译必须保证整体过程的协调进行.在翻译过程中,也 ...

  2. [译] ES6+ 中的 JavaScript 工厂函数(第八部分)

    本文讲的是[译] ES6+ 中的 JavaScript 工厂函数(第八部分), 原文地址:JavaScript Factory Functions with ES6+ 原文作者:Eric Elliot ...

  3. 深入Java泛型(四):RxJava中泛型的使用分析

    RxJava出现在我们的视线已经很久了,我自己也有阅读过非常多的文章,谈不上精通,但是勉强称得上会一些简单的使用,近日总是对这种响应式的编程,对RxJava魂牵梦绕,深刻的感觉到自己对泛型的认识,理解 ...

  4. RxJava中常见的几种Subject

    RxJava是什么? 原文是这样描述的: RxJava is a Java VM implementation of Reactive Extensions: a library for compos ...

  5. [译]Kotlin中是应该使用序列(Sequences)还是集合(Lists)?

    翻译说明: 原标题: Sequences - a Pragmatic Approach 原文地址: https://proandroiddev.com/sequences-a-pragmatic-ap ...

  6. matlab preloadfcn,运行xilinx blockset中的错误包含在matlab中

    xilinx 14.1和matlab2012a 当我打开一个系统生成器时,它会在matlab命令窗口中显示错误,如下所示: 警告:xbsIndex.mdl,第7行:评估block_diagram'xb ...

  7. 计算机视觉:Bag of words算法实现过程中出现错误及解决方案

    Bag of words算法实现过程中出现错误及解决方案 出现的问题 IndexError: list index out of range OSError:x.sift not found sqli ...

  8. 有关高级关系引擎中存在错误

    SQL server2005 Analysis Services项目中出现以下错误 高级关系引擎中存在错误. 无法与 DataSourceID 为"Adventure Works DW&qu ...

  9. python怎么查看代码错误_python中的错误如何查看

    python常见的错误有 1.NameError变量名错误 2.IndentationError代码缩进错误 3.AttributeError对象属性错误 4.TypeError类型错误 5.IOEr ...

最新文章

  1. torch.nn.softmax()
  2. Info:Memory module [DIMM] needs attention: Single-bit warning error rate exceeded, Single-bit fai...
  3. cad线性标注命令_CAD图纸中怎么进行线性标注
  4. 两台服务器安装redis集群_Redis Cluster搭建高可用Redis服务器集群
  5. java服务器和linux_在Linux下开一个Java服务器(使用CatServer Pro)
  6. 最全BAT数据库面试89题:mysql、大数据、redis
  7. String 常用API
  8. FCN用卷积层代替FC层原因(转)
  9. python使用repeat、cycle重复打印字符串
  10. 语音信号处理(九)——离散余弦变换
  11. 5个冷门的MacOS快捷键,小众但好用
  12. 【数据蒋堂】报表应用的三层结构
  13. CSS实现tag标签挂载放到卡片上
  14. oracle 日期型函数转换,oracle中,日期转换函数
  15. NVIDIA Jetson TK1学习与开发——手动刷机
  16. React - 红绿灯
  17. 一个拼图软件项目Demo
  18. STM32F10X SPI操作flash MX25L64读写数据
  19. try 在java中的含义_java中try的含义
  20. 程序员如何保持身体健康

热门文章

  1. xinit启动X Window System过程初探(转)
  2. Google开启全新图像目标检测大赛,场景多样性和复杂度史无前例
  3. 美团正押注无人车?没错,这是外卖大战的第三阶段
  4. MOSS 2018 回顾:向 40 余个开源项目捐赠 97 万美元
  5. tfs 2013 access deny
  6. Android反编译方法
  7. c++的set_unexpected不起作用
  8. MySql noinstall-5.1.34-win32 配置
  9. Linux下双网卡绑定技术实现负载均衡和失效保护 bond
  10. 基于MicroPython的家庭可燃气体泄露微信报警器