非同期処理を書くときにFuture/Promiseパターンは便利です。C++11とかEcmaScript6とか様々な言語に浸透した感があって、もちろんScalaにもあります。
さて、ScalaのFutureに後からタイムアウトを付けたい場合は、標準でAwait.result[T](awaitable: Awaitable[T], atMost: Duration)
が用意されていて、複数のFutureを一斉に待ちあわせたい時はfor内包表記(yield使うやつ)で合成しておけば良さそうです。モナド……!
が、この合成は元のFutureが一つでも失敗すると全体的に失敗してしまうので、Futureそれぞれで成功したものは結果を取得したいし失敗したときはデフォルト値にしておきたい、といった場合だと単純には使えません。
で、どうするんだろうと思ったらやはり同様の質問があって、
Akkaライブラリのakka.pattern.after
を使う回答があります。
implicit class FutureHelper[T](f: Future[T]) extends AnyVal{
import akka.pattern.after
def orDefault(t: Timeout, default: => T)(implicit system: ActorSystem): Future[T] = {
val delayed = after(t.duration, system.scheduler)(Future.successful(default))
Future firstCompletedOf Seq(f, delayed)
}
}
implicit classなのでFutureから直接orDefault
を呼びだせる(ように書ける)というわけですね。便利!
defaultが名前渡しなのはafter[T](duration: FiniteDuration, using: Scheduler)(value: ⇒ Future[T])
に合わせて、タイムアウト時をカウントする前にデフォルト値の評価時間を食わないためですね。
この手法の利点は解説記事があって、
Akkaのドキュメントにも載っています。