この記事では既存のライブラリなどでコールバックやPromiseを使っている非同期関数が既にある場合に、awaitで結果を渡す方法について説明します。
async/awaitの説明で良くあるコードはこんな感じです。
fun async1() = async(CommonPool) {
delay(1000)
return@async 1
}
fun async2() = async(CommonPool) {
delay(1000)
return@async 2
}
fun testAsync() = launch(UI) {
val result1 = async1().await()
info { "result1: $result1" }
val result2 = async2().await()
info { "result2: $result2" }
}
このコードを実行すると期待通り、実行して1秒後にresult1: 1
が表示され、さらに1秒後にresult2: 2
が表示されます。
なんの問題もありません。
で、今回はfirestoreからデータ取得する部分でawait使えるようにしたかったので、こんな感じの関数を作りました。
return@async 1
してる部分を,firestoreから取得したデータのインスタンスを返すイメージです。
fun async1() = async(CommonPool) {
firestore.document("/employees/1").get().addOnSuccessListener { documentSnapshot ->
val employee = documentSnapshot.toObject(Employee::class.java)
return@async employee
}.addOnFailureListener {
error { "fetch employee failed" }
return@async null
}
}
すると return@async employee
で 'return' is not allowed here
と怒られます。
確かにLambda式の中だから直接asyncのreturnができないのか。
でもどうすればいいのかわからずググっていたところ、suspendCoroutine
なるキーワードを見つけました。
次のように使用します。期待する戻り値の型(Employee
)をgenericsで指定して、cont.resume(employee)
で結果を返します。Promiseのresolveやrxのobserver.nextのようなイメージですね。
fun async1() = async(CommonPool) {
val employee = suspendCoroutine<Employee> { cont ->
firestore.document("/employees/1").get().addOnSuccessListener { documentSnapshot ->
val employee = documentSnapshot.toObject(Employee::class.java)
cont.resume(employee)
}.addOnFailureListener {
error { "fetch employee failed" }
cont.resumeWithException(it)
}
}
return@async employee
}
呼び出し側はこんな感じ。
fun testAsync() = launch(UI) {
val employee = async1().await()
info { "name: ${employee.name}" }
}
Coroutinさわって数時間のビギナーなんで、もっといい方法があるのかも。
とりあえず動いた方法の紹介でした。
マサカリ歓迎。