Coroutinesで役立つResult型

nomoa,KotlincoroutineAndroid

Kotlin1.3にてCoroutinesが正式採用となった。  https://blog.jetbrains.com/kotlin/2018/09/kotlin-1-3-rc-is-here-migrate-your-coroutines/ (opens in a new tab)

Coroutinesの正式採用と同時にCoroutineのエラーハンドリング時に役に立つResult型がStandard Libraryとして名前を変更して導入された。 今回は私がそのResult型をどのように利用したか述べる。

基本

fun main(args: Array<String>) {
    val result: Result<String> = runCatching {
        throw Error("in runCatching")
    }

    println(result)
}
// 結果 Failure(java.lang.Error: in runCatching)

runCatching内で発生したerrorはReslut型としてcatchされ処理を中断させることなく継続できる。

何が嬉しいか

ここで画像をロードしてリクエストが完了した後にviewに反映させたいケースがあるとする まずはResult型を使わない例で考えてみる。

fun main(args: Array<String>) {
    GlobalScope.launch(Dispatchers.Default) {// AndroidではここでDispatchers.Mainを使う
        val model = async(Dispatchers.IO) {
            try {
                loadSomething()
            } catch (e: Error) {
                e.printStackTrace()
                null
            }
        }.await()

        if (model != null) {
            updateView(model)
        } else {
            errorHandling()
        }
    }
    Thread.sleep(100000)
}

次にResult型を使用して書いた場合。

fun main(args: Array<String>) {
    GlobalScope.launch(Dispatchers.Default) {// AndroidではここでDispatchers.Mainを使う
        val result = async(Dispatchers.IO) {
            runCatching {
                loadSomething()
            }
        }.await()

        result
            .onSuccess {
                updateView(it)
            }
            .onFailure {
                it.printStackTrace()
                errorHandling()
            }
    }
    Thread.sleep(100000)
}

nullを返すことによってerror処理をしていた場合よりもより記述的になってよい。 nullableのモデルを返すよりもこのerrorは処理を継続して返さなければならないことが伝わって良いコードになった。

recover

結果が失敗していた際にラムダ内でデフォルトの値を入れたいときなどに使う。

fun main(args: Array<String>) {
    GlobalScope.launch(Dispatchers.Default) {// AndroidではここでDispatchers.Mainを使う
        val result = async(Dispatchers.IO) {
            runCatching {
                throw Error("in run catching")
            }
        }.await()

        val transformedModel = result
            .recover {
                TransformedModel("default value")
            }
        println(transformedModel)
    }
    Thread.sleep(100000)
}
// Success(TransformedModel(value=default value))

例で試しているようにrecover内で生成した好きな型を返せるので注意する。recoverメソッドを使用する際はおそらく成功の際に使用するModel型を返したいときに使うはずなのでなぜこのように自由さを持たせているか不明(何か使うケースあったら教えてください。)

map

recoverとは逆に成功した際にモデルを変換するために使います。Errorとは分けてnullableの型をnonNullに変えたいから?

fun main(args: Array<String>) {
    GlobalScope.launch(Dispatchers.Default) {// AndroidではここでDispatchers.Mainを使う
        val result = async(Dispatchers.IO) {
            runCatching {
                loadSomething()
            }
        }.await()

        val transformedModel = result
            .map { model ->
                model.transform()
            }
        println(transformedModel)
    }
    Thread.sleep(100000)
}

data class Model(val value: String) {
    fun transform(): TransformedModel {
        return TransformedModel(value)
    }
}

private fun loadSomething(): Model {
    return Model("this is model")
}
// Success(TransformedModel(value=this is model))

fold

上の二つを合わせたメソッド。こちらはReuslt型ではなく変換された値で帰ってきて使い勝手がいいので実務でも使うケースがあ。

fun main(args: Array<String>) {
    GlobalScope.launch(Dispatchers.Default) {// AndroidではここでDispatchers.Mainを使う
        val result = async(Dispatchers.IO) {
            runCatching {
                loadSomething()
            }
        }.await()

        val transformedModel: TransformedModel = result
            .fold({ value: Model ->
                value.transform()
            }, { exception: Throwable ->
                TransformedModel("default value")
            })

        println(transformedModel)
    }
    Thread.sleep(100000)
}
// TransformedModel(value=this is model)

現在Result型は直接関数の返り値としては使用することができない。理由は将来的な拡張性を残したいからだそう。 https://github.com/Kotlin/KEEP/blob/master/proposals/stdlib/result.md (opens in a new tab) Limitationから引用

The rationale behind these limitations is that future versions of Kotlin may expand and/or change semantics of functions that return Result type and null-safety operators may change their semantics when used on values of Result type. In order to avoid breaking existing code in the future releases of Kotlin and leave door open for those changes, the corresponding uses produce an error now.

今までerror handlingするためにボイラープレートなコードを書いていた。車輪の再発明を無くすためにもJetbrains blogはチェックする。

© nomoa.devRSS