Scala: List[Future] to Future[List] disregarding failed futures

前端 未结 6 552
终归单人心
终归单人心 2020-11-29 16:28

I\'m looking for a way to convert an arbitrary length list of Futures to a Future of List. I\'m using Playframework, so ultimately, what I really want is a Future[Resu

相关标签:
6条回答
  • 2020-11-29 16:48

    The trick is to first make sure that none of the futures has failed. .recover is your friend here, you can combine it with map to convert all the Future[T] results to Future[Try[T]]] instances, all of which are certain to be successful futures.

    note: You can use Option or Either as well here, but Try is the cleanest way if you specifically want to trap exceptions

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

    Then use Future.sequence as before, to give you a Future[List[Try[T]]]

    val futureListOfTrys = Future.sequence(listOfFutureTrys)
    

    Then filter:

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

    You can even pull out the specific failures, if you need them:

    val futureListOfFailures = futureListOfTrys.map(_.filter(_.isFailure))
    
    0 讨论(0)
  • 2020-11-29 16:52

    Scala 2.12 has an improvement on Future.transform that lends itself in an anwser with less codes.

    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)))
    
    0 讨论(0)
  • 2020-11-29 16:53

    I just came across this question and have another solution to offer:

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

    The idea here is that within the fold you are waiting for the next element in the list to complete (using the for-comprehension syntax) and if the next one fails you just fallback to what you already have.

    0 讨论(0)
  • 2020-11-29 16:53

    You can also collect successful and unsuccessful results in different lists:

    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) }
        }
      }
    }
    
    0 讨论(0)
  • 2020-11-29 17:08

    I tried Kevin's answer, and I ran into a glitch on my version of Scala (2.11.5)... I corrected that, and wrote a few additional tests if anyone is interested... here is my version >

    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)
      }
    
    0 讨论(0)
  • 2020-11-29 17:12

    You can easily wraps future result with option and then flatten the 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
    
    0 讨论(0)
提交回复
热议问题