原文地址:https://www.bennyhuo.com/2019/04/23/coroutine-exceptions/
异步代码的异常处理通常都比较让人头疼,而协程则再一次展现了它的威力。
引子
我们在前面一篇文章当中提到了这样一个例子:
typealias Callback = (User) -> Unit
fun getUser(callback: Callback){
...
}
我们通常会定义这样的回调接口来实现异步数据的请求,我们可以很方便的将它转换成协程的接口:
suspend fun getUserCoroutine() = suspendCoroutine<User> {
continuation ->
getUser {
continuation.resume(it)
}
}
并最终交给按钮点击事件或者其他事件去触发这个异步请求:
getUserBtn.setOnClickListener {
GlobalScope.launch(Dispatchers.Main) {
userNameView.text = getUserCoroutine().name
}
}
那么问题来了,既然是请求,总会有失败的情形,而我们这里并没有对错误的处理,接下来我们就完善这个例子。
添加异常处理逻辑
首先我们加上异常回调接口函数:
interface Callback<T> {
fun onSuccess(value: T)
fun onError(t: Throwable)
}
接下来我们在改造一下我们的 getUserCoroutine:
suspend fun getUserCoroutine() = suspendCoroutine<User> {
continuation ->
getUser(object : Callback<User> {
override fun onSuccess(value: User) {
continuation.resume(value)
}
override fun onError(t: Throwable) {
continuation.resumeWithException(t)
}
})
}
大家可以看到,我们似乎就是完全把 Callback 转换成了一个 Continuation,在调用的时候我们只需要:
GlobalScope.launch(Dispatchers.Main) {
try {
userNameView.text = getUserCoroutine().name
} catch (e: Exception) {
userNameView.text = "Get User Error: $e"
}
}
是的,你没看错,一个异步的请求异常,我们只需要在我们的代码中捕获就可以了,这样做的好处就是,请求的全流程异常都可以在一个 try … catch … 当中捕获,那么我们可以说真正做到了把异步代码变成了同步的写法。
如果你一直在用 RxJava 处理这样的逻辑,那么你的请求接口可能是这样的:
fun getUserObservable(): Single<User> {
return Single.create<User> {
emitter ->
getUser(object : Callback<User> {
override fun onSuccess(value: User) {
emitter.onSuccess(value)
}
override fun onError(t: Throwable) {
emitter.onError(t)
}
})
}
}
调用时大概是这样的:
getUserObservable()
.observeOn(AndroidSchedulers.mainThread())
.subscribe ({
user ->
userNameView.text = user.name
}, {
userNameView.text = "Get User Error: $it"
})
其实你很容易就能发现在这里 RxJava 做的事儿跟协程的目的是一样的,只不过协程用了一种更自然的方式。
也许你已经对 RxJava 很熟悉并且感到很自然,但相比之下,RxJava 的代码比协程的复杂度更高,更让人费解,这一点我们后面的文章中也会持续用例子来说明这一点。
全局异常处理
线程也好、RxJava 也好,都有全局处理异常的方式,例如: