Scala : 실패한 Future를 무시하고 List [Future]에서 Future [List]로


116

임의 길이의 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이면에서 호출 합니다.

도움을 주셔서 미리 감사드립니다!

답변:


147

트릭은 먼저 실패한 미래가 없는지 확인하는 것입니다. .recover여기에있는 친구입니다. map모든 Future[T]결과를 Future[Try[T]]]인스턴스 로 변환하기 위해 결합 할 수 있으며 , 모두 성공적인 미래가 될 것입니다.

참고 : 사용할 수있는 OptionEither뿐만 아니라 여기지만, 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))

감사! .recover나에게 실종 된 부분이었다.

20
당신이 사용할 수있는 _.collect{ case Success(x) => x}대신에 _.filter(_.isSuccess)제거하는 Try유형에서 futureListOfSuccesses.
senia

43
스칼라 2010 년에 .recover(x => Failure(x))유효하지 않은 사용 .recover({case e => Failure(e)})대신에
FGRibreau

미래 래퍼가 누락 된 것 같습니다. def futureToFutureOfTry [A] (f : Future [A]) : Future [Try [A]] = {val p = Promise [Try [A]] () f.map {a => p.success (scala.util.Success (a))} .recover {case x : Throwable => p.success (Failure (x))} p.future}
Dario

별로. 나는 중간에 약속이 필요하지 않은, 또 다른 미래로 미래를 매핑하고있어 낭비입니다
케빈 라이트

12

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)))

11

나는 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)
  }

7

나는 방금이 질문을 보았고 제공 할 또 다른 해결책이 있습니다.

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())
}

여기서 아이디어는 폴드 내에서 목록의 다음 요소가 완료되기를 기다리고 (이해를위한 구문 사용) 다음 요소가 실패하면 이미 가지고있는 요소로 대체된다는 것입니다.


나는 당신이 일을 끝낼 방법 같은 이름하지만 난 싫어, 바로 시퀀스 IMPL에서
crak

1

옵션을 사용하여 향후 결과를 쉽게 래핑 한 다음 목록을 병합 할 수 있습니다.

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

누군가가 첫 번째 함수에서 Some에 오류가 발생하는 경우 컴파일러 오류를 방지하기 위해 첫 번째 함수를 다음과 같이 다시 작성할 수 있습니다. def futureToFutureOption [T] (f : Future [T]) : Future [Option [T]] = f.map (Option (_)). recover {case e => None}
Zee

0

다른 목록에서 성공 및 실패한 결과를 수집 할 수도 있습니다.

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) }
    }
  }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.