1. ホーム
  2. scala

[解決済み] Scalaです。リスト[Future]からFuture[List]への変換は、失敗したFutureを無視する。

2022-08-11 18:50:20

質問

任意の長さのFutureのリストをFuture of Listに変換する方法を探しています。私はPlayframeworkを使っているので、最終的に、私が本当に欲しいのは Future[Result] ですが、物事を単純化するために、単に次のように言ってみましょう。 Future[List[Int]] 通常の方法では、このように Future.sequence(...) となるのですが、一工夫が...。与えられたリストには通常10-20のフューチャーがあり、そのうちの1つが失敗することも珍しくありません(それらは外部のウェブサービスへのリクエストを行っています)。そのうちの1つが失敗した場合、それらすべてを再試行するのではなく、成功したものを取得してそれを返すことができればと思います。

例えば、以下のようにしてもうまくいきません。

import scala.concurrent._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.Success
import scala.util.Failure

val listOfFutures = Future.successful(1) :: Future.failed(new Exception("Failure")) :: 
                    Future.successful(3) :: Nil

val futureOfList = Future.sequence(listOfFutures)

futureOfList onComplete {
  case Success(x) => println("Success!!! " + x)
  case Failure(ex) => println("Failed !!! " + ex)
}

scala> Failed !!! java.lang.Exception: Failure

例外だけを取得するのではなく、そこから1や3を引き出せるようにしたい。試しに Future.fold を使ってみましたが、これはどうやら Future.sequence を呼び出しているだけです。

よろしくお願いします。

どのように解決するのですか?

まず、どの先物も失敗していないことを確認するのがコツです。 .recover はここであなたの味方です、あなたはこれを map と組み合わせることで、すべての Future[T] の結果を Future[Try[T]]] インスタンスに変換され、そのすべてが成功した先物となることが確実です。

note: あなたは Option または Either も同様ですが、ここでは Try は最もクリーンな方法です。

def futureToFutureTry[T](f: Future[T]): Future[Try[T]] =
  f.map(Success(_)).recover { case x => Failure(x)}

val listOfFutures = ...
val listOfFutureTrys = listOfFutures.map(futureToFutureTry(_))

次に Future.sequence を使うと、先ほどと同じように Future[List[Try[T]]]

val futureListOfTrys = Future.sequence(listOfFutureTrys)

次にフィルタリングします。

val futureListOfSuccesses = futureListOfTrys.map(_.filter(_.isSuccess))

必要であれば、具体的な失敗例も引き出せます。

val futureListOfFailures = futureListOfTrys.map(_.filter(_.isFailure))