目前,我有类似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
委托结果?
答案 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客户端库。它具有一些发送请求的方法,例如httpGet
和httpPost
。他们接受回调。
您有大约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
和异常。
这可能不是您所需要的。知道这是一个错误响应,您打算如何处理响应?在上面的示例中,我编写了一些标准逻辑,这些逻辑从响应中创建异常。如果需要,您可以捕获该异常并在呼叫站点提供自定义逻辑。