こんにちは, Web事業部の西村です
私の前回の記事 では dyanmic の利用をなくすようにしました
そして今回は, Callbackの呼び出しをやめて, JavaScriptの非同期処理( Promise )になるよう変更したいと思います
また, Promise は スタンダード なものと Coroutines のものの2種類がありますので, この記事ではその両方を紹介したいと思います
目次
過去の記事
なぜ非同期に?
非同期処理することにより複数の処理を同時に実行することができます
例えば, 複数のファイルを受け取り, S3に保存する といったことを考えてみます
非同期処理ではない場合, 1ずつしかファイルのアップロードができません
非同期処理の場合, 複数のファイルを同時にアップロードできるようになるので時間の短縮が図れます
注意事項
この記事では Promise の詳しい解説は行いません
詳しく知りたい方は下記の記事を参考にしてください
開発環境
この記事では下記の環境で開発を行っています
- AdoptOpenJDK 1.8.0_222-b10
- IntelliJ IDEA Community 2019.2.2
- Kotlin 1.3.50
プロジェクト
前回の記事 まで作成したプロジェクトを利用します
プロジェクトの構成は下記のようになっています
1KotlinLambda
2├─.gradle/
3├─.idea/
4├─build/
5├─gradle/
6├─src/
7│ └─main/
8│ └─kotlin/
9│ └─jp.co.seeds_std.lambda.kotlin/
10│ └─handler.kt
11├─build.gradle.kts
12├─compress.zip
13├─gradlew
14├─gradlew.bat
15└─settings.gradle.kts
また, handler.ktは下記のようになっています
1package jp.co.seeds_std.lambda.kotlin
2
3import kotlin.js.Json
4import kotlin.js.Promise
5import kotlin.js.json
6
7@JsExport
8@JsName("handler")
9fun handler(event: Json, context: Json, callback: (Error?, Response?) -> Unit) {
10 val response = Response(body = "Hello from Kotlin Class Lambda!")
11 callback(null, response)
12}
13
14data class Response(
15 val statusCode: Int = 200,
16 val body: String = "{}",
17 val isBase64Encoded: Boolean = false,
18 val headers: Json = json(),
19 val multiValueHeaders: Json = json()
20)
スタンダードなPromise
まずはkotlin-stdlibに実装されている Promise を利用してみます
関数の書き換え
handler 関数を変更します
handler.kt
1@JsExport
2@JsName("handler")
3fun handler(event: Json, context: Json, callback: (Error?, Response?) -> Unit) {
4 val response = Response(body = "Hello from Kotlin Class Lambda!")
5 callback(null, response)
6}
↓
1@JsExport
2@JsName("handler")
3fun handler(event: Json, context: Json) = Promise<Response> { executor, reject ->
4 executor(Response(body = "Hello from Kotlin Async Lambda!"))
5}
- executor は処理成功時に実行する関数で, 引数はジェネリクスで指定したクラス (この記事では Response ) となります
- reject は処理失敗時に実行する関数で, 引数は Throwable クラスのオブジェクトとなります
動作確認
関数をデプロイし, API GatewayのURLにアクセスし Hello from Kotlin Async Lambda! と表示されれば成功です
もう少し恩恵を受けてみる(スタンダード)
今度はNode.jsの setTimeout を利用して本当に非同期に処理されているかを確認したいと思います
その前に, Kotlin/JSにはNode.jsの setTimeout は定義されていないので追加します
今回追加する場所はコードの最下部に追加します
handler.kt
1external fun setTimeout(callback: dynamic, delay: Int, vararg args: Any?): Int
2external fun clearTimeout(timeout: Int)
- external : 外部の関数/変数を利用するために記述します。トランスパイルした場合にそのまま残るようになります
- callback の部分が dynamic となっていますが, 関数の引数が固定値ではないためやむを得ず dynamic としています
続いて handler 関数を変更します
handler.kt
1@JsExport
2@JsName("handler")
3fun handler(event: Json, context: Json) = Promise<Response> { executor, reject ->
4 val results = (1..10).map {
5 Promise<Int> { childExecutor, _ ->
6 setTimeout({childExecutor(it)}, 1000)
7 }
8 }
9 Promise.all(results.toTypedArray()).then {
10 executor(Response(body = "Result: ${it.sum()} - Standard"))
11 }
12}
このコードでは 1秒待ってから数字を返す というコードを10回繰り返すものとなっています
動作確認
関数をデプロイし, API GatewayのURLにアクセスし約1秒後に Result: 55 - Standard と表示されれば成功です
※初回の実行では2-3秒ほどかかる場合もあります
通常の処理であれば 1秒待って数字を返す を10回行えば10秒かかるはずですが, 非同期に実行されているため約1秒で完了します
CoroutinesのPromise
標準実装の Promise では見にくく感じませんでしたか?(私は少なからず見にくいなと感じました)
非同期処理を書くのであれば, Node.jsみたいに async で書きたいところです
そこで使うのが kotlinx.coroutines です
依存関係を追加する
kotlinx.coroutines は外部ライブラリなので build.gradle.kts に依存関係を追加する必要があります
build.gradle.kts
1implementation(kotlin("stdlib-js"))
2implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:1.3.2") // 追加
コードを書き換える
まずはただの文字列を返すように handelr 関数を変更します
handler.kt
1@JsExport
2@JsName("handler")
3fun handler(event: Json, context: Json) = GlobalScope.promise {
4 return@promise Response(body = "Hello from Kotlin Coroutines Lambda!")
5}
先ほどのコードと比較するとかなり単調なものになりました
※エラーを返す際は throw NullPointerException() のように throw するだけとなります
動作確認
関数をデプロイし, API GatewayのURLにアクセスし Hello from Kotlin Coroutines Lambda! と表示されれば成功です
もう少し恩恵を受けてみる(Coroutines)
先ほどと同じ関数を実装してみます
handler.kt
1@JsExport
2@JsName("handler")
3fun handler(event: Json, context: Json) = GlobalScope.promise {
4 val tasks = (1..10).map {
5 async {
6 delay(1000)
7 it
8 }
9 }
10 val results = tasks.awaitAll()
11 return@promise Response(body = "Result: ${results.sum()} - Coroutines")
12}
先ほどとは異なり, CoroutineScope 内で実行できるため, async 関数を利用できます
動作確認
関数をデプロイし, API GatewayのURLにアクセスし約1秒後に Result: 55 - Coroutines と表示されれば成功です
※初回の実行では2-3秒ほどかかる場合もあります
最後に
いかがでしたでしょうか
非同期処理が行えるとよりできることの幅が増えるかと思います
また、Coroutinesの Promise と標準実装の Promise は互換性があるため外部ライブラリが非同期処理を行う場合にも使え,
よりKotlinらしい書き方ができるようになっていくと思います
次回は kotlinx.serialization を用いてJsonのレスポンスを行う記事を書きたいと思います
ここまで読んでいただきありございました