协程委托异常

时间:2018-09-26 04:12:27

标签: java kotlin kotlinx.coroutines

目前,我有类似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来将结果与异常一起委派。

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

  3. 是否有更好的方法可以在不使用回调的情况下从getMovies委托结果?

2 个答案:

答案 0 :(得分:0)

我不确定您是否真的需要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的内容。如果您只想在当前代码中将任何异常传递给回调,则可以在放置评论callback.onException(RuntimeException())的地方使用I want to delegate exceptions here.

(请注意,我正在使用Kotlin 1.3,现在是RC ...)

答案 1 :(得分:0)

  

是否有更好的方法可以在不使用回调的情况下从getMovies委托结果?

让我从一些假设开始:

  • 您正在使用一些异步HTTP客户端库。它具有一些发送请求的方法,例如httpGethttpPost。他们接受回调。

  • 您有大约20种方法,例如fetchMovies发送HTTP请求。

我建议为每个发送请求的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对象的解析逻辑,但是我认为这不会影响该方法。

  

我目前正在从回调中迁移此代码,因此我有大约20个api调用,并且我不想在各处添加try/catch来将结果与异常一起委派。

您不必在每个电话旁都插入try-catch。整理您的代码,这样您就可以让异常向上传播给调用者,并在您处理异常的中心位置。如果您无法做到这一点,则意味着您已经有了处理每种异常的特定方法。那么try-catch是最好的惯用选项。如果您有一个简单的阻止API,那么它将是您要编写的内容。特别要注意的是,将多个HTTP调用包装在一个try-catch中是多么琐碎的事情,您无法通过回调来复制这些东西。

  

我只能从暂挂函数中获取异常,有什么办法可以同时获取HttpResponse和异常。

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