Scala: Liệt kê [Tương lai] đến [Danh sách] tương lai bỏ qua những tương lai thất bại


116

Tôi đang tìm cách chuyển đổi danh sách Hợp đồng tương lai có độ dài tùy ý thành Danh sách tương lai. Tôi đang sử dụng Playframework, vì vậy cuối cùng, những gì tôi thực sự muốn là một Future[Result], nhưng để làm cho mọi thứ đơn giản hơn, hãy nói Future[List[Int]]Cách thông thường để làm điều này là sử dụng Future.sequence(...)nhưng có một chút thay đổi ... Danh sách tôi đưa ra thường có khoảng 10-20 hợp đồng tương lai trong đó, và không có gì lạ khi một trong những hợp đồng tương lai đó không thành công (họ đang thực hiện các yêu cầu dịch vụ web bên ngoài). Thay vì phải thử lại tất cả chúng trong trường hợp một trong số chúng không thành công, tôi muốn có thể lấy những cái đã thành công và trả lại những cái đó.

Ví dụ, làm như sau không hiệu quả

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

Thay vì nhận được ngoại lệ duy nhất, tôi muốn có thể kéo 1 và 3 ra khỏi đó. Tôi đã thử sử dụng Future.fold, nhưng đó dường như chỉ là các cuộc gọi Future.sequenceđằng sau hậu trường.

Xin được cảm ơn trước về sự giúp đỡ!

Câu trả lời:


146

Bí quyết là trước tiên hãy đảm bảo rằng không có hợp đồng tương lai nào thất bại. .recoverlà bạn của bạn ở đây, bạn có thể kết hợp nó với mapđể chuyển đổi tất cả các Future[T]kết quả thành các Future[Try[T]]]phiên bản, tất cả đều chắc chắn là tương lai thành công.

lưu ý: Bạn có thể sử dụng Optionhoặc Eithercũng có thể ở đây, nhưng Trylà cách sạch nhất nếu bạn đặc biệt muốn bẫy các trường hợp ngoại lệ

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

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

Sau đó, sử dụng Future.sequencenhư trước đây, để cung cấp cho bạnFuture[List[Try[T]]]

val futureListOfTrys = Future.sequence(listOfFutureTrys)

Sau đó lọc:

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

Bạn thậm chí có thể lấy ra các lỗi cụ thể, nếu bạn cần chúng:

val futureListOfFailures = futureListOfTrys.map(_.filter(_.isFailure))

Cảm ơn! .recoverthực sự là mảnh ghép còn thiếu đối với tôi.
Joe

20
Bạn có thể sử dụng _.collect{ case Success(x) => x}thay vì _.filter(_.isSuccess)để loại bỏ Trytrong loại futureListOfSuccesses.
senia

43
Trong scala 2010 .recover(x => Failure(x))là không hợp lệ, sử dụng .recover({case e => Failure(e)})thay vì
FGRibreau

Tôi nghĩ bạn đang thiếu trình bao bọc trong tương lai: 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

không phải vậy. Tôi đang lập bản đồ một tương lai tương lai khác, một Promise can thiệp là không cần thiết và sẽ là lãng phí
Kevin Wright

12

Scala 2.12 có một cải tiến về Future.transformkhả năng cho vay chính nó trong một trình cảm ứng với ít mã hơn.

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

Tôi đã thử câu trả lời của Kevin, và tôi đã gặp trục trặc trên phiên bản Scala (2.11.5) của mình ... Tôi đã sửa lỗi đó và viết thêm một vài bài kiểm tra nếu ai đó quan tâm ... đây là phiên bản của tôi>

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

Tôi vừa gặp câu hỏi này và có một giải pháp khác để cung cấp:

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

Ý tưởng ở đây là trong màn hình đầu tiên, bạn đang đợi phần tử tiếp theo trong danh sách hoàn thành (sử dụng cú pháp để hiểu) và nếu phần tử tiếp theo không thành công, bạn chỉ cần dự phòng lại những gì bạn đã có.


Tôi không thích cái tên nhưng tôi thích cách nó được thực hiện, trực tiếp từ impl chuỗi
crak

1

Bạn có thể dễ dàng kết thúc kết quả trong tương lai với tùy chọn và sau đó làm phẳng danh sách:

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

Chỉ trong trường hợp người khác gặp lỗi với Some trong hàm đầu tiên, hàm đầu tiên có thể được viết lại như vậy để tránh lỗi trình biên dịch: def futureToFutureOption [T] (f: Future [T]): Future [Option [T]] = f.map (Option (_)). recovery {case e => None}
Zee

0

Bạn cũng có thể thu thập kết quả thành công và không thành công trong các danh sách khác nhau:

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) }
    }
  }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.