Я ищу способ конвертировать произвольный список длин Futures в будущее списка. Я использую Playframework, поэтому, в конечном счете, я действительно хочу, чтобы Future[Result]
, но чтобы сделать вещи проще, давайте просто скажем Future[List[Int]]
. Обычный способ сделать это - использовать Future.sequence(...)
, но там есть твист.. Список, который мне предоставлен, обычно содержит около 10-20 фьючерсов, и это не редкость для отказа одного из этих фьючерсов (они делают запросы внешнего веб-сервиса). Вместо того, чтобы повторять все из них в случае, если один из них терпит неудачу, я хотел бы иметь возможность получить то, что удалось, и вернуть их.
Например, выполнение следующих действий не работает
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]]]
, все из которых наверняка будут успешными фьючерсами.
Примечание: здесь вы можете использовать Option
или Either
, но Try
- самый чистый способ, если вы специально хотите использовать исключения
def futureToFutureTry[T](f: Future[T]): Future[Try[T]] =
f.map(Success(_)).recover(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))
Я попробовал ответить Кевину, и я столкнулся с ошибкой в моей версии Scala (2.11.5)... Я исправил это и написал несколько дополнительных тестов, если кому-то интересно... вот моя версия >
implicit class FutureCompanionOps(val f: Future.type) extends AnyVal {
/** Given a list of futures `fs`, returns the future holding the list of Try 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)
}
Я только наткнулся на этот вопрос и предлагаю другое решение:
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())
}
Идея здесь заключается в том, что в пределах складки вы ожидаете завершения следующего элемента в списке (используя синтаксис для понимания), а если следующий не удастся, вы просто отбросьте то, что у вас уже есть.
Scala 2.12 имеет улучшение на Future.transform
, которое поддается андерсеру с меньшим количеством кодов.
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)))
.recover
был действительно недостающим предметом для меня._.collect{ case Success(x) => x}
вместо_.filter(_.isSuccess)
чтобы избавиться отTry
в типеfutureListOfSuccesses
.