【问题标题】:Coroutines delegate exceptions协程委托异常
【发布时间】:2018-09-26 04:12:26
【问题描述】:

目前,我有一些类似 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 委托结果??

【问题讨论】:

  • 您在此处发布的代码不起作用...inline 丢失,object : Callback&lt;T&gt; 和界面不匹配...除此之外...我可能真的不明白为什么您正在使用该构造...不会像以下内容就足够了吗? fun getMovies(callback: Callback&lt;Movie&gt;) = GlobalScope.launch(UI) { val job = async { fetchMovies() }; job.getCompletionExceptionOrNull()?.apply(callback::onException); job.await().forEach(callback::onComplete) }(注意我使用的是 Kotlin 1.3)?
  • 我不能使用异步。我必须使用我在上面创建的 awaitCallback 函数 bcz 我将 java 回调包装在一个挂起函数中。
  • “我必须使用 awaitCallback [...] bcz 我将 java 回调包装在一个挂起函数中”是什么意思?在之前的评论中我也做了同样的事情,但我使用了async 作为示例......请注意传递给launch 的块函数已经是suspend 函数。因此,如果您使用suspendCancellableCoroutine 的唯一原因是调用不是suspend 的函数,您可以省略它而只使用launch 的块函数,它本身就是suspend 函数。或者你为什么在这里使用suspendCancellableCoroutine
  • 顺便说一句:你写了“上面的作品”(没有显示我使用它仍然会遇到一些编译错误)......你在fetchMovies-function中做了什么?
  • 好的...实际上我在评论中的代码是错误的(但是不是很多);-) 应该事先测试过...仍然...我很难理解为什么会这样awaitCallback/suspendCancellableCoroutine 真的很需要。

标签: java kotlin kotlinx.coroutines


【解决方案1】:

有没有更好的方法可以在不使用回调的情况下从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 和异常。

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

【讨论】:

    【解决方案2】:

    我不确定你是否真的需要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.

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

    【讨论】:

    • 关于fetchMovies 的签名是对的,它目前需要callback,它有Void 返回,但它没有try/catch,它只是从@ 实现HttpResponse 987654341@ 最终将结果归类为onSuccess/onFailure。我尝试了您使用async 的最后一种方法ide 抱怨job.getCompletionExceptionOrNull()?.also 中的东西未使用(对于失败情况也是如此)。基本上我试图避免使用try/catch 来处理异常,因为我有很多 api 调用。
    • 未使用?可能不是……你确定你是 1:1 复制的吗?如果您的fetchMovies 采用Callback,但没有处理异常,那么您运气不好,需要将其包装在try/catch 中。你可以控制fetchMovies-方法吗?您的所有方法是否都像这样,即接受回调但没有处理那里的异常?然后,您可能宁愿将您的awaitCallback 与我第一次显示的getMovies-method 类似的函数交换...但不要将其称为getMovies,而是将其重命名为awaitCallback,然后将其传递给您然后使用的实际函数在async.
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-13
    相关资源
    最近更新 更多