はじめに
Scala で複数の Future
を扱う際、皆様はどのように実装していますか?
Scala には複数の Future
を1つにまとめる Future.sequence
という便利なメソッドがあります。
ただし、 Future.sequence
を使う場合、 Future は並列に実行されます。
ほとんどのケースで並列に実行されても問題ない(パフォーマンスとしても望ましい)のですが、直列に実行したいケースがあります。
具体的には、複数の API コールを行う際に同時接続数が制限されている場合です。
Future
は作成された時点で実行されてしまうため、直列に実行するには少し工夫が必要になります。
問題
以下のような API のモックがあります。
引数を10倍し、 100ms 待って返すだけのものです。
この API を10回コールした際の動作を検証します。
(検証用コードなのでこのようになっていますが、本来は Thread.sleep
を Future
に使ってはいけません!)
def someApiCall(foo: Int): Future[Int] = {
Future {
println(s"start $foo")
Thread.sleep(100)
println(s"end $foo")
foo * 10
}
}
並列に実行
Await.result({
Future.sequence({
(1 to 10).map(someApiCall)
})
.map(println)
}, 100.seconds)
start 2
start 8
start 3
start 7
start 4
start 1
start 5
start 6
end 7
end 2
end 6
end 3
end 4
end 8
end 1
end 5
start 9
start 10
end 10
end 9
Vector(10, 20, 30, 40, 50, 60, 70, 80, 90, 100)
上のように、並列に実行すると開始・終了はバラバラになります。
直列に実行
Await.result({
(1 to 10).foldLeft[Future[Seq[Int]]](Future.successful(Nil))({ (results$, i) =>
for {
results <- results$
result <- someApiCall(i)
} yield results :+ result
})
.map(println)
}, 100.seconds)
start 1
end 1
start 2
end 2
start 3
end 3
start 4
end 4
start 5
end 5
start 6
end 6
start 7
end 7
start 8
end 8
start 9
end 9
start 10
end 10
List(10, 20, 30, 40, 50, 60, 70, 80, 90, 100)
前の API コールが終了してから次が呼ばれています。
おわりに
あまりきれいとは言えないコードなので、改善案がございましたらご指摘くださると幸いです。