LoginSignup
20
19

More than 5 years have passed since last update.

Coroutinesで役立つResult型

Last updated at Posted at 2019-02-02

はじめに

Kotlin1.3にてCoroutinesが正式採用となりました。 
https://blog.jetbrains.com/kotlin/2018/09/kotlin-1-3-rc-is-here-migrate-your-coroutines/

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

基本

main.kt
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型を使わない例で考えてみます。

main.kt
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型を使用して書いた場合です。

main.kt
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

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

main.kt
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に変えたいから?

main.kt
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型ではなく変換された値で帰ってきて使い勝手がいいので実務でも使うケースがありそうです。

main.kt
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
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はチェックしていこう。

20
19
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
19