Scala: List [Future] to Future [List]

Szukam sposobu na konwersję dowolnej długości listy kontraktów Futures na przyszłość listy. Używam Playframework, więc ostatecznie, to czego naprawdę chcę to Future[Result], ale żeby było prościej, powiedzmy Future[List[Int]] normalnym sposobem na to byłoby użycie Future.sequence(...), ale jest pewien zwrot akcji... Lista, którą otrzymuję, zwykle zawiera około 10-20 kontraktów futures i nie jest rzadkością, że jedna z tych kontraktów futures zawodzi (robią zewnętrzne żądania usług internetowych). Zamiast ponawiać próby wszystkich w jeśli jeden z nich zawiedzie, chciałbym być w stanie dostać się do tych, które odniosły sukces i zwrócić je.

Na przykład wykonywanie następujących czynności nie działa

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

Zamiast dostać jedyny wyjątek, chciałbym być w stanie wyciągnąć 1 i 3 stamtąd. Próbowałem użyć Future.fold, ale to najwyraźniej wywołuje Future.sequence Za kulisami.

Z góry dzięki za pomoc!

Author: Joe, 2014-01-02

5 answers

Sztuką jest najpierw upewnić się, że żadna z kontraktów futures nie zawiedzie. .recover jest twoim przyjacielem tutaj, możesz połączyć go z map, Aby przekonwertować wszystkie wyniki Future[T] na Future[Try[T]]] instancje, z których wszystkie są pewne, że będą udane futures.

uwaga: możesz użyć Option lub Either, ale Try jest najczystszym sposobem, jeśli chcesz pułapkować wyjątki

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

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

Następnie użyj Future.sequence jak wcześniej, aby dać Future[List[Try[T]]]

val futureListOfTrys = Future.sequence(listOfFutureTrys)

Wtedy filtr:

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

Możesz nawet wyciągnąć konkretne awarie, jeśli ich potrzebujesz:

val futureListOfFailures = futureListOfTrys.map(_.filter(_.isFailure))
 115
Author: Kevin Wright,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-02 00:45:17

Próbowałem odpowiedzi Kevina i natrafiłem na usterkę w mojej wersji Scali (2.11.5)... Poprawiłem to i napisałem kilka dodatkowych testów, jeśli ktoś jest zainteresowany... oto moja wersja >

implicit class FutureCompanionOps(val f: Future.type) extends AnyVal {

    /** Given a list of futures `fs`, returns the future holding the list of Try's of the futures from `fs`.
      * The returned future is completed only once all of the futures in `fs` have been completed.
      */
    def allAsTrys[T](fItems: /* future items */ List[Future[T]]): Future[List[Try[T]]] = {
      val listOfFutureTrys: List[Future[Try[T]]] = fItems.map(futureToFutureTry)
      Future.sequence(listOfFutureTrys)
    }

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

    def allFailedAsTrys[T](fItems: /* future items */ List[Future[T]]): Future[List[Try[T]]] = {
      allAsTrys(fItems).map(_.filter(_.isFailure))
    }

    def allSucceededAsTrys[T](fItems: /* future items */ List[Future[T]]): Future[List[Try[T]]] = {
      allAsTrys(fItems).map(_.filter(_.isSuccess))
    }
}


// Tests... 



  // allAsTrys tests
  //
  test("futureToFutureTry returns Success if no exception") {
    val future =  Future.futureToFutureTry(Future{"mouse"})
    Thread.sleep(0, 100)
    val futureValue = future.value
    assert(futureValue == Some(Success(Success("mouse"))))
  }
  test("futureToFutureTry returns Failure if exception thrown") {
    val future =  Future.futureToFutureTry(Future{throw new IllegalStateException("bad news")})
    Thread.sleep(5)            // need to sleep a LOT longer to get Exception from failure case... interesting.....
    val futureValue = future.value

    assertResult(true) {
      futureValue match {
        case Some(Success(Failure(error: IllegalStateException)))  => true
      }
    }
  }
  test("Future.allAsTrys returns Nil given Nil list as input") {
    val future =  Future.allAsTrys(Nil)
    assert ( Await.result(future, 100 nanosecond).isEmpty )
  }
  test("Future.allAsTrys returns successful item even if preceded by failing item") {
    val future1 =  Future{throw new IllegalStateException("bad news")}
    var future2 = Future{"dog"}

    val futureListOfTrys =  Future.allAsTrys(List(future1,future2))
    val listOfTrys =  Await.result(futureListOfTrys, 10 milli)
    System.out.println("successItem:" + listOfTrys);

    assert(listOfTrys(0).failed.get.getMessage.contains("bad news"))
    assert(listOfTrys(1) == Success("dog"))
  }
  test("Future.allAsTrys returns successful item even if followed by failing item") {
    var future1 = Future{"dog"}
    val future2 =  Future{throw new IllegalStateException("bad news")}

    val futureListOfTrys =  Future.allAsTrys(List(future1,future2))
    val listOfTrys =  Await.result(futureListOfTrys,  10 milli)
    System.out.println("successItem:" + listOfTrys);

    assert(listOfTrys(1).failed.get.getMessage.contains("bad news"))
    assert(listOfTrys(0) == Success("dog"))
  }
  test("Future.allFailedAsTrys returns the failed item and only that item") {
    var future1 = Future{"dog"}
    val future2 =  Future{throw new IllegalStateException("bad news")}

    val futureListOfTrys =  Future.allFailedAsTrys(List(future1,future2))
    val listOfTrys =  Await.result(futureListOfTrys,  10 milli)
    assert(listOfTrys(0).failed.get.getMessage.contains("bad news"))
    assert(listOfTrys.size == 1)
  }
  test("Future.allSucceededAsTrys returns the succeeded item and only that item") {
    var future1 = Future{"dog"}
    val future2 =  Future{throw new IllegalStateException("bad news")}

    val futureListOfTrys =  Future.allSucceededAsTrys(List(future1,future2))
    val listOfTrys =  Await.result(futureListOfTrys,  10 milli)
    assert(listOfTrys(0) == Success("dog"))
    assert(listOfTrys.size == 1)
  }
 9
Author: Chris Bedford,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-16 01:34:20

Właśnie natknąłem się na to pytanie i mam do zaoferowania inne rozwiązanie:

def allSuccessful[A, M[X] <: TraversableOnce[X]](in: M[Future[A]])
                                                (implicit cbf: CanBuildFrom[M[Future[A]], A, M[A]], 
                                                 executor: ExecutionContext): Future[M[A]] = {
    in.foldLeft(Future.successful(cbf(in))) {
      (fr, fa) ⇒ (for (r ← fr; a ← fa) yield r += a) fallbackTo fr
    } map (_.result())
}

Chodzi o to, że w fold czekasz na następny element listy do uzupełnienia (używając składni for-comprehension), a jeśli następny zawiedzie, po prostu wycofujesz się do tego, co już masz.

 6
Author: Idan Waisman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-10 21:35:23

Możesz łatwo zawinąć przyszły wynik opcją, a następnie spłaszczyć listę:

def futureToFutureOption[T](f: Future[T]): Future[Option[T]] =
    f.map(Some(_)).recover {
      case e => None
    }
val listOfFutureOptions = listOfFutures.map(futureToFutureOption(_))

val futureListOfOptions = Future.sequence(listOfFutureOptions)

val futureListOfSuccesses = futureListOfOptions.flatten
 1
Author: Amir Hossein Javan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-05 05:06:29

Scala 2.12 ma ulepszenie Future.transform, które nadaje się do anwsera z mniejszą ilością kodów.

val futures = Seq(Future{1},Future{throw new Exception})

val seq = Future.sequence(futures.map(_.transform(Success(_)))) // instead of map and recover

@val successes = seq.map(_.collect{case Success(x)=>x})
successes: Future[Seq[Int]] = Future(Success(List(1)))

@val failures = seq.map(_.collect{case Failure(x)=>x})
failures: Future[Seq[Throwable]] = Future(Success(List(java.lang.Exception)))
 0
Author: WeiChing Lin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-07 03:37:36