协程委托异常

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)
}

问题

  1. 上面的方法可行,但是有没有更好的方法呢??最主要的事情之一是我目前正在从回调中迁移这段代码,所以我有 ~20 api 个调用,我不想在任何地方添加 try/catch 来委托结果和异常。

  2. 此外,我只能从我的挂起函数中获取 exception 有没有办法同时获取 HttpResponse 和异常。或者是否可以使用现有的 JAVA 界面。

  3. 有没有更好的方法在不使用回调的情况下委托 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 客户端库。它有一些发送请求的方法,例如httpGethttpPost。他们接受回调。

  • 您有 ~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.

这可能不是您需要的。知道这是一个错误响应,您究竟打算如何处理响应?在上面的示例中,我编写了一些标准逻辑来从响应中创建异常。如果必须,您可以捕获该异常并在调用站点提供自定义逻辑。