임의 길이의 Futures 목록을 Future of List로 변환하는 방법을 찾고 있습니다. 저는 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 { 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))
답변
Scala 2.12는 Future.transform
코드가 적은 anwser에 적합 하도록 개선되었습니다 .
val futures = Seq(Future{1},Future{throw new Exception})
// instead of `map` and `recover`, use `transform`
val seq = Future.sequence(futures.map(_.transform(Success(_))))
val successes = seq.map(_.collect{case Success(x)=>x})
successes
//res1: Future[Seq[Int]] = Future(Success(List(1)))
val failures = seq.map(_.collect{case Failure(x)=>x})
failures
//res2: Future[Seq[Throwable]] = Future(Success(List(java.lang.Exception)))
답변
나는 Kevin의 대답을 시도했고 내 버전의 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'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)
}
답변
나는 방금이 질문을 보았고 제공 할 또 다른 해결책이 있습니다.
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())
}
여기서 아이디어는 폴드 내에서 목록의 다음 요소가 완료되기를 기다리고 (이해를위한 구문 사용) 다음 요소가 실패하면 이미 가지고있는 요소로 대체된다는 것입니다.
답변
옵션을 사용하여 향후 결과를 쉽게 래핑 한 다음 목록을 병합 할 수 있습니다.
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
답변
다른 목록에서 성공 및 실패한 결과를 수집 할 수도 있습니다.
def safeSequence[A](futures: List[Future[A]]): Future[(List[Throwable], List[A])] = {
futures.foldLeft(Future.successful((List.empty[Throwable], List.empty[A]))) { (flist, future) =>
flist.flatMap { case (elist, alist) =>
future
.map { success => (elist, alist :+ success) }
.recover { case error: Throwable => (elist :+ error, alist) }
}
}
}