web-dev-qa-db-ja.com

延期された結果を変更する

モデルを返すAPI( Retrofit によって実装)が与えられます。拡張関数を使用して、昔ながらの CallDeferredにラップします。

_fun <T> Call<T>.toDeferred(): Deferred<T> {
    val deferred = CompletableDeferred<T>()

    // cancel request as well
    deferred.invokeOnCompletion {
        if (deferred.isCancelled) {
            cancel()
        }
    }

    enqueue(object : Callback<T> {
        override fun onFailure(call: Call<T>?, t: Throwable) {
            deferred.completeExceptionally(t)
        }

        override fun onResponse(call: Call<T>?, response: Response<T>) {
            if (response.isSuccessful) {
                deferred.complete(response.body()!!)
            } else {
                deferred.completeExceptionally(HttpException(response))
            }
        }
    })

    return deferred
}
_

これで、次のようなモデルを取得できます。

_data class Dummy(val name: String, val age: Int)

fun getDummy(): Deferred<Dummy> = api.getDummy().toDeferred()
_

しかし、どうすればDeferred内のオブジェクトをmodifyして、Deferredを返すことができますか。

_fun getDummyAge(): Deferred<Int> {
    // return getDummy().age
}
_

私はコルーチンに慣れていないので、これはここで物事が行われる方法ではないかもしれません。私が RxJava ファンだとすると、次のようにこのケースを実装します。

_fun getDummy(): Single<Dummy> = api.getDummy().toSingle()

fun getDummyAge(): Single<Int> = getDummy().map { it.age }
_

では、Deferred関数からgetDummyAgeを返そうとすべきでしょうか?または、可能な場合はいつでも_suspended fun_を宣言し、すべてのAPIのメソッドでdeferred.await()を呼び出す方がよいでしょうか?

9

async styleのプログラミングに従う場合、つまり_Deferred<T>_を返す関数を作成する場合は、async functiongetDummyAgeを次のように定義できます。

_fun getDummyAge(): Deferred<Int> = async { getDummy().await().age }
_

ただし、このスタイルのプログラミングは、Kotlinでは一般的に推奨されていません。慣用的なKotlinアプローチは、次の署名を使用して拡張関数の一時停止Call<T>.await()を定義することです。

_suspend fun <T> Call<T>.await(): T = ... // more on it later
_

そして、それを使用して次のように記述しますサスペンド関数getDummyタイプの結果を返すDummy直接遅延にラップせずに:

_suspend fun getDummy(): Dummy = api.getDummy().await()
_

この場合、簡単に書くことができますサスペンド関数getDummyAge

_suspend fun getDummyAge(): Int = getDummy().age
_

Retrofit呼び出しの場合、次のようにawait拡張機能を実装できます。

_suspend fun <T> Call<T>.await(): T = suspendCancellableCoroutine { cont ->
    cont.invokeOnCompletion { cancel() }
    enqueue(object : Callback<T> {
        override fun onFailure(call: Call<T>?, t: Throwable) {
            cont.resumeWithException(t)
        }

        override fun onResponse(call: Call<T>?, response: Response<T>) {
            if (response.isSuccessful) {
                cont.resume(response.body()!!)
            } else {
                cont.resumeWithException(HttpException(response))
            }
        }
    })
}
_

非同期関数とサスペンド関数のスタイルの違いについて詳しく知りたい場合は、KotlinConf 2017の コルーチンの概要 をご覧になることをお勧めします。次に、 設計ドキュメントのこのセクション もいくつかの洞察を提供します。

21
Roman Elizarov