Kotlin の Coroutines を5分で理解する

Kotlin の Coroutines を理解するのに何も難しいことはありません。そう、JavaScript の async/await がわかればね。

まず、 `1` から `10` までの値を合算する処理があったとします。

```kotlin
fun main(args: Array<String>) {
    val numList = (1..10)
    val sum = numList.reduce { acc, v -> acc + v }
    println(sum) // 55
}
```

JS でいう Promise は Coroutines の世界では Deferred という名称です。これを生成する関数を用意します。

```kotlin
import kotlinx.coroutines.*

fun createDeferred(value: Int): Deferred<Int> {
    return GlobalScope.async {
        delay(1000)
        value    
    }
}
```

上記は JS でいうと下記のようなコードに相当します。

```js
function createPromise(value) {
  return new Promise(resolve => setTimeout(() => resolve(value), 1000));
}
```

「`GlobalScope.async` で非同期処理を行う Deferred を生成できる」と理解すれば一旦は OK です。

Deferred を捌く `main()` は下記のように変わります。

```kotlin
suspend fun main(args: Array<String>) { // *1
    val deferredList = (1..10).map { v -> createDeferred(v) } // *2
    val numList = deferredList.awaitAll() // *3
    val sum = numList.reduce { acc, v -> acc + v }
    println(sum) // 55 (約1秒遅れ)
}
```

- \*1 ... `suspend fun` は `async function` 相当だという理解で OK
- \*2 ... 変数 `deferredList` の型は `List<Deferred<Int>>`
- \*3 ... `await Promise.all()` みたいなことをやっているという理解で OK

上記のコードは途中で await をしているので、関数宣言に `suspend` を付けないとエラーになります。

ここまでで正常系は理解できました。
ではエラーハンドリングはどうしたらいいでしょうか。

まず `createDeferred()` を下記のように変更します。

```kotlin
import kotlin.random.Random

fun createDeferred(value: Int): Deferred<Int> {
    return GlobalScope.async {
        delay(1000)
        when (Random.nextFloat() < 0.9) { // *1, *2
            true -> value
            else -> throw Exception("Error for $value")
        }
    }
}
```

- \*1 ... `Random.nextFloat()` は `0` 以上 `1` 未満の数値を返す
- \*2 ... `when` でパターンマッチングを表現できる

これにより `createDeferred()` で返される Deferred は10%の確率でエラーになります。

となると `main()` ではエラーハンドリングをしないといけません。

```kotlin
suspend fun main(args: Array<String>) {
    val deferredList = (1..10).map { v -> createDeferred(v) }
    try {
        val numList = deferredList.awaitAll()
        val sum = numList.reduce { acc, v -> acc + v }
    	println(sum) // 55
    } catch (e: Exception) {
        println(e) // "Error for n"
    }
}
```

何も難しいことはなく、await するところを try catch で囲むだけです。
このコードでは10%の確率でエラーになるものを10個束ねているので、ほとんどのケースで catch の中を通ってエラーが出力されますが、たまに成功して `55` が出力されます。

最終的に全容はこのようになりました。

```kotlin
import kotlinx.coroutines.*
import kotlin.random.Random

suspend fun main(args: Array<String>) {
    val deferredList = (1..10).map { v -> createDeferred(v) }
    try {
        val numList = deferredList.awaitAll()
        val sum = numList.reduce { acc, v -> acc + v }
    	println(sum)    
    } catch (e: Exception) {
        println(e)
    }
}

fun createDeferred(value: Int): Deferred<Int> {
    return GlobalScope.async {
        delay(1000)
        when (Random.nextFloat() < 0.9) {
            true -> value
            else -> throw Exception("Error for $value")
        }
    }
}
```

( https://pl.kotl.in/H1LuMeSWV ← 上記コードを実際に動かせる Kotlin Playground )

ちなみに Deferred は下記のようにして個別に await することもできます。

```kotlin
suspend fun main(args: Array<String>) {
    val deferredList = (1..10).map { v -> createDeferred(v) }
    val numList = deferredList.map { d -> 
    	try {
            d.await()
        } catch (e: Exception) {
            println(e)
            0
        }
    }
    val sum = numList.reduce { acc, v -> acc + v }
    println(sum)
}
```

以上です。

とても簡単に Coroutines を理解することができました。

特に TypeScript に慣れていれば型の書き方はほとんど同じだし、非同期処理に関しては用語が違うくらいなのでとっつきやすいと思いました。