博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[译] RxJava 中的错误处理
阅读量:7126 次
发布时间:2019-06-28

本文共 3279 字,大约阅读时间需要 10 分钟。

本文讲的是[译] RxJava 中的错误处理,
  • 原文地址:
  • 原文作者:
  • 译文出自:
  • 本文永久链接:
  • 译者:
  • 校对者: 

DrawingDrawing

一旦你开始使用 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
( val data: T?, val error: Throwable?)

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

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

一些有用的代码段

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

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

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

class UserProvider {  fun getUsers(): Observable
>> { return myRetrofitApi.getUsers() .retrofitResponseToResult() }}
原文发布时间为:2017年8月30日
本文来自云栖社区合作伙伴掘金,了解相关信息可以关注掘金网站。

转载地址:http://mjoel.baihongyu.com/

你可能感兴趣的文章
聊聊rocketmq的DailyRollingFileAppender
查看>>
HTTP/2
查看>>
[单刷APUE系列]第十七章——高级进程间通信
查看>>
分布式之消息队列的特点、选型、及应用场景详解
查看>>
多迪学员问到最多的问题:为什么要学习Python编程语言?
查看>>
从vue中学习defineProperty
查看>>
漂亮的颜色
查看>>
Android Volley 源码解析(二),探究缓存机制
查看>>
Go源码剖析:内置类型
查看>>
102. Binary Tree Level Order Traversal
查看>>
SAP云平台对Kubernetes的支持
查看>>
原来实现GCP用客户端登录这么简单啊
查看>>
PAT A1057 分块思想
查看>>
PAT A1007 动态规划
查看>>
VUE父子组件传递数据
查看>>
前端知识点——图片
查看>>
别人家的程序员是如何使用 Java 进行 Web 抓取的?
查看>>
95%的技术面试必考的JVM知识点都在这,另附加分思路!
查看>>
日期类问题
查看>>
区块链入门之基础知识
查看>>