协程委托异常
Coroutines delegate exceptions
目前,我有一些类似 this 的场景,其中我有 java 接口回调,看起来像这样。
Java回调
interface Callback<T> {
void onComplete(T result)
void onException(HttpResponse response, Exception ex)
}
上面的暂停函数看起来像这样
suspend inline fun <T> awaitCallback(crossinline block: (Callback<T>) -> Unit) : T =
suspendCancellableCoroutine { cont ->
block(object : Callback<T> {
override fun onComplete(result: T) = cont.resume(result)
override fun onException(e: Exception?) {
e?.let { cont.resumeWithException(it) }
}
})
}
我的调用函数如下所示
fun getMovies(callback: Callback<Movie>) {
launch(UI) {
awaitCallback<Movie> {
// I want to delegate exceptions here.
fetchMovies(it)
}
}
我目前正在做的捕捉异常的是这个
fun getMovies(callback: CallbackWrapper<Movie>) {
launch(UI) {
try{
val data = awaitCallback<Movie> {
// I want to delegate exceptions here.
fetchMovies(it)
}
callback.onComplete(data)
}catch(ex: Exception) {
callback.onFailure(ex)
}
}
}
// I have to make a wrapper kotlin callback interface for achieving the above
interface CallbackWrapper<T> {
fun onComplete(result: T)
fun onFailure(ex: Exception)
}
问题
上面的方法可行,但是有没有更好的方法呢??最主要的事情之一是我目前正在从回调中迁移这段代码,所以我有 ~20 api 个调用,我不想在任何地方添加 try/catch
来委托结果和异常。
此外,我只能从我的挂起函数中获取 exception
有没有办法同时获取 HttpResponse
和异常。或者是否可以使用现有的 JAVA 界面。
有没有更好的方法在不使用回调的情况下委托 getMovies
的结果??
我不确定您是否真的需要 awaitCallback
。
如果你真的有很多 Callback
已经到位,这就是你使用它的原因,那么你的函数可能已经准备好与 Callback
一起正常工作的所有内容,例如我期望一些方法如下:
fun fetchMovies(callback : Callback<List<Movie>>) {
try {
// get some values from db or from a service...
callback.onComplete(listOf(Movie(1), Movie(2)))
} catch (e : Exception) {
callback.onFailure(e)
}
}
如果您没有这样的东西,您甚至可能根本不需要 awaitCallback
。因此,如果您的 fetchMovies
函数具有如下签名:
fun fetchMovies() : List<Movie>
并且在 getMovies
中你传递了你的 Callback
,那么你可能只需要一个简单的 async
,例如:
fun getMovies(callback: Callback<List<Movie>>) {
GlobalScope.launch { // NOTE: this is now a suspend-block, check the parameters for launch
val job = async { fetchMovies() }
try {
callback.onComplete(job.await())
} catch (e: Exception) {
callback.onException(e)
}
}
}
这个示例当然可以更改为许多类似的变体,例如以下内容也将起作用:
fun getMovies(callback: Callback<List<Movie>>) {
GlobalScope.launch { // NOTE: this is now a suspend-block, check the parameters for launch
val job = async { fetchMovies() } // you could now also cancel/await, or whatever the job
job.join() // we just join now as a sample
job.getCompletionExceptionOrNull()?.also(callback::onFailure)
?: job.getCompleted().also(callback::onComplete)
}
}
您还可以添加类似 job.invokeOnCompletion
的内容。如果您只是想在当前代码中将任何异常传递给您的回调,您可以在您放置评论 I want to delegate exceptions here.
.
的地方使用 callback.onException(RuntimeException())
(请注意,我使用的是 Kotlin 1.3,它现在是一个 RC...)
Is there any better way to delegate the result from getMovies
without using callback?
让我从一些假设开始:
您正在使用一些异步 HTTP 客户端库。它有一些发送请求的方法,例如httpGet
和httpPost
。他们接受回调。
您有 ~20 种发送 HTTP 请求的方法,例如 fetchMovies
。
我建议为每个发送请求的 HTTP 客户端方法创建一个扩展 suspend fun
。例如,这会将异步 client.httpGet()
变成暂停 client.awaitGet()
:
suspend fun <T> HttpClient.awaitGet(url: String) =
suspendCancellableCoroutine<T> { cont ->
httpGet(url, object : HttpCallback<T> {
override fun onComplete(result: T) = cont.resume(result)
override fun onException(response: HttpResponse?, e: Exception?) {
e?.also {
cont.resumeWithException(it)
} ?: run {
cont.resumeWithException(HttpException(
"${response!!.statusCode()}: ${response.message()}"
))
}
}
})
}
基于此你可以写suspend fun fetchMovies()
或任何其他的:
suspend fun fetchMovies(): List<Movie> =
client.awaitGet("http://example.org/movies")
我的简化示例缺少将 HTTP 响应转换为 Movie
对象的解析逻辑,但我认为这不会影响该方法。
I'm currently migrating this code from callback so I have ~20 api calls and I don't want to add try/catch
everywhere to delegate the result along with the exception.
您不需要 try-catch
围绕每个单独的调用。组织你的代码,这样你就可以让异常向上传播到调用者,并有一个处理异常的中心位置。如果你做不到,这意味着你有一个特定的方法来处理每个异常;那么 try-catch
是最好的惯用选项。如果你有一个简单的阻塞 API,这就是你会写的。特别要注意将许多 HTTP 调用包装在一个 try-catch
中是多么微不足道,这是您无法通过回调复制的东西。
I'm only able to get exception from my suspending function is there any way to get both HttpResponse
as well as the exception.
这可能不是您需要的。知道这是一个错误响应,您究竟打算如何处理响应?在上面的示例中,我编写了一些标准逻辑来从响应中创建异常。如果必须,您可以捕获该异常并在调用站点提供自定义逻辑。
目前,我有一些类似 this 的场景,其中我有 java 接口回调,看起来像这样。
Java回调
interface Callback<T> {
void onComplete(T result)
void onException(HttpResponse response, Exception ex)
}
上面的暂停函数看起来像这样
suspend inline fun <T> awaitCallback(crossinline block: (Callback<T>) -> Unit) : T =
suspendCancellableCoroutine { cont ->
block(object : Callback<T> {
override fun onComplete(result: T) = cont.resume(result)
override fun onException(e: Exception?) {
e?.let { cont.resumeWithException(it) }
}
})
}
我的调用函数如下所示
fun getMovies(callback: Callback<Movie>) {
launch(UI) {
awaitCallback<Movie> {
// I want to delegate exceptions here.
fetchMovies(it)
}
}
我目前正在做的捕捉异常的是这个
fun getMovies(callback: CallbackWrapper<Movie>) {
launch(UI) {
try{
val data = awaitCallback<Movie> {
// I want to delegate exceptions here.
fetchMovies(it)
}
callback.onComplete(data)
}catch(ex: Exception) {
callback.onFailure(ex)
}
}
}
// I have to make a wrapper kotlin callback interface for achieving the above
interface CallbackWrapper<T> {
fun onComplete(result: T)
fun onFailure(ex: Exception)
}
问题
上面的方法可行,但是有没有更好的方法呢??最主要的事情之一是我目前正在从回调中迁移这段代码,所以我有 ~20 api 个调用,我不想在任何地方添加
try/catch
来委托结果和异常。此外,我只能从我的挂起函数中获取
exception
有没有办法同时获取HttpResponse
和异常。或者是否可以使用现有的 JAVA 界面。有没有更好的方法在不使用回调的情况下委托
getMovies
的结果??
我不确定您是否真的需要 awaitCallback
。
如果你真的有很多 Callback
已经到位,这就是你使用它的原因,那么你的函数可能已经准备好与 Callback
一起正常工作的所有内容,例如我期望一些方法如下:
fun fetchMovies(callback : Callback<List<Movie>>) {
try {
// get some values from db or from a service...
callback.onComplete(listOf(Movie(1), Movie(2)))
} catch (e : Exception) {
callback.onFailure(e)
}
}
如果您没有这样的东西,您甚至可能根本不需要 awaitCallback
。因此,如果您的 fetchMovies
函数具有如下签名:
fun fetchMovies() : List<Movie>
并且在 getMovies
中你传递了你的 Callback
,那么你可能只需要一个简单的 async
,例如:
fun getMovies(callback: Callback<List<Movie>>) {
GlobalScope.launch { // NOTE: this is now a suspend-block, check the parameters for launch
val job = async { fetchMovies() }
try {
callback.onComplete(job.await())
} catch (e: Exception) {
callback.onException(e)
}
}
}
这个示例当然可以更改为许多类似的变体,例如以下内容也将起作用:
fun getMovies(callback: Callback<List<Movie>>) {
GlobalScope.launch { // NOTE: this is now a suspend-block, check the parameters for launch
val job = async { fetchMovies() } // you could now also cancel/await, or whatever the job
job.join() // we just join now as a sample
job.getCompletionExceptionOrNull()?.also(callback::onFailure)
?: job.getCompleted().also(callback::onComplete)
}
}
您还可以添加类似 job.invokeOnCompletion
的内容。如果您只是想在当前代码中将任何异常传递给您的回调,您可以在您放置评论 I want to delegate exceptions here.
.
callback.onException(RuntimeException())
(请注意,我使用的是 Kotlin 1.3,它现在是一个 RC...)
Is there any better way to delegate the result from
getMovies
without using callback?
让我从一些假设开始:
您正在使用一些异步 HTTP 客户端库。它有一些发送请求的方法,例如
httpGet
和httpPost
。他们接受回调。您有 ~20 种发送 HTTP 请求的方法,例如
fetchMovies
。
我建议为每个发送请求的 HTTP 客户端方法创建一个扩展 suspend fun
。例如,这会将异步 client.httpGet()
变成暂停 client.awaitGet()
:
suspend fun <T> HttpClient.awaitGet(url: String) =
suspendCancellableCoroutine<T> { cont ->
httpGet(url, object : HttpCallback<T> {
override fun onComplete(result: T) = cont.resume(result)
override fun onException(response: HttpResponse?, e: Exception?) {
e?.also {
cont.resumeWithException(it)
} ?: run {
cont.resumeWithException(HttpException(
"${response!!.statusCode()}: ${response.message()}"
))
}
}
})
}
基于此你可以写suspend fun fetchMovies()
或任何其他的:
suspend fun fetchMovies(): List<Movie> =
client.awaitGet("http://example.org/movies")
我的简化示例缺少将 HTTP 响应转换为 Movie
对象的解析逻辑,但我认为这不会影响该方法。
I'm currently migrating this code from callback so I have ~20 api calls and I don't want to add
try/catch
everywhere to delegate the result along with the exception.
您不需要 try-catch
围绕每个单独的调用。组织你的代码,这样你就可以让异常向上传播到调用者,并有一个处理异常的中心位置。如果你做不到,这意味着你有一个特定的方法来处理每个异常;那么 try-catch
是最好的惯用选项。如果你有一个简单的阻塞 API,这就是你会写的。特别要注意将许多 HTTP 调用包装在一个 try-catch
中是多么微不足道,这是您无法通过回调复制的东西。
I'm only able to get exception from my suspending function is there any way to get both
HttpResponse
as well as the exception.
这可能不是您需要的。知道这是一个错误响应,您究竟打算如何处理响应?在上面的示例中,我编写了一些标准逻辑来从响应中创建异常。如果必须,您可以捕获该异常并在调用站点提供自定义逻辑。