はじめに
Scalaプログラマである僕のjavascriptのasync/awaitの勉強メモです。
で、ScalaプログラマにとってはFutureがasync/await(Promise)に似たモデルなので、それに絡めて理解したことをまとめようと思った次第です。
本編
async/awaitの説明を読むと「asyncをつけるとPromiseを返すfunctionになる。awaitするとそれの値をとり出せる」みたいな説明が出てきます。まぁ言われることはわかるんだけど、何となく心の底から理解できてない感じがありました。
で、以下の説明を読んでいるとScalaのFutureと絡めてとても理解しやすい例がありました。
非同期関数 - Promise をわかりやすくする | Web | Google Developers
引用します。
同期的に見えるコードを記述している場合でも、並列で実行するチャンスを見逃さないようにしてください。
async function series() {
await wait(500);
await wait(500);
return "done!";
}
上記は完了するのに 1000 ミリ秒かかります。
async function parallel() {
const wait1 = wait(500);
const wait2 = wait(500);
await wait1;
await wait2;
return "done!";
}
一方、上記は両方の待機が同時に発生するため、完了までにかかる時間は 500 ミリ秒です。
これを見ると以下のようなプログラムと非常に似ているなとかんじます。
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.Duration
import scala.concurrent.{ Await, Future }
def wait(msec: Int) = {
Future(Thread.sleep(msec))
}
def series() = {
Await.result(wait(500), Duration.Inf)
Await.result(wait(500), Duration.Inf)
"done!"
}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.Duration
import scala.concurrent.{ Await, Future }
def wait(msec: Int) = {
Future(Thread.sleep(msec))
}
def series() = {
val wait1 = wait(500)
val wait2 = wait(500)
Await.result(wait1, Duration.Inf)
Await.result(wait2, Duration.Inf)
"done!"
}
ただ、大きく違う部分が2点あります。
- javascriptの
series
はそれ自身がasync functionなのでscalaのseries
のFuture
を返すべき- というかasync functionの中でしかawaitを使えない
- javascriptの実行エンジンはシングルスレッドであり、
await
はブロックしない1。await
以降の行はawait
の引数の処理が終わった後に実行されるコールバック処理になっている。- scalaの方は
wait
の処理を別スレッドが実行し、Await.result
では別スレッドの実行終了をメインスレッドがブロックして待っている。
- scalaの方は
この2つの事実からより近いプログラムは以下のような、await
処理のところをfor
式で置き換えたようなものだと理解しました。
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
def wait(msec: Int) = {
Future(Thread.sleep(msec))
}
def series() = {
for {
_ <- wait(500)
_ <- wait(500)
} yield "done!"
}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
def wait(msec: Int) = {
Future(Thread.sleep(msec))
}
def series() = {
val wait1 = wait(500)
val wait2 = wait(500)
for {
_ <- wait1
_ <- wait2
} yield "done!"
}
さすがにwait
の処理が別スレッドで実行されるところはjavascriptとは違いますが、こういうふうに理解しておくとjavascriptのasync/awaitのコードが理解しやすくなりそうです。
-
関数定義の前に async キーワードを使用すると、その関数内に await を使用できます。 Promise を await する場合、この関数は、ブロックすることなく Promise が完了状態になるまで一時停止します。 ( https://developers.google.com/web/fundamentals/getting-started/primers/async-functions?hl=ja ) ↩