JavaScript

Scalaプログラマがjavascriptのasync/awaitを勉強したメモ

More than 1 year has passed since last update.

はじめに

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 ミリ秒です。

これを見ると以下のようなプログラムと非常に似ているなとかんじます。

1000msecかかる
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!"
}
500msecかかる
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のseriesFutureを返すべき
    • というかasync functionの中でしかawaitを使えない
  • javascriptの実行エンジンはシングルスレッドであり、awaitはブロックしない1await以降の行はawaitの引数の処理が終わった後に実行されるコールバック処理になっている。
    • scalaの方はwaitの処理を別スレッドが実行し、Await.resultでは別スレッドの実行終了をメインスレッドがブロックして待っている。

この2つの事実からより近いプログラムは以下のような、await処理のところをfor式で置き換えたようなものだと理解しました。

1000msecかかる
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!"
}
500msecかかる
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のコードが理解しやすくなりそうです。


  1. 関数定義の前に async キーワードを使用すると、その関数内に await を使用できます。 Promise を await する場合、この関数は、ブロックすることなく Promise が完了状態になるまで一時停止します。 ( https://developers.google.com/web/fundamentals/getting-started/primers/async-functions?hl=ja