本文共 3279 字,大约阅读时间需要 10 分钟。
- 原文地址:
- 原文作者:
- 译文出自:
- 本文永久链接:
- 译者:
- 校对者:
一旦你开始使用 RxJava 函数库写代码,你会发现一些东西能有很多不同的实现方式,但是有时你很难立即确定哪种方法最好,错误处理就是其中之一。
那么,在 RxJava 中处理错误的最佳方法是什么,又有哪些选择呢?
假设你有一个 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) } })
这可能看起来不错,但它有一些缺陷:
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
类,你可以使用这个方便的扩展功能:
funObservable .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() }}
转载地址:http://mjoel.baihongyu.com/