Scala: รายการ [Future] to Future [List] โดยไม่คำนึงถึงฟิวเจอร์สที่ล้มเหลว


116

ฉันกำลังมองหาวิธีแปลงรายการ Futures ที่มีความยาวโดยพลการเป็น Future of List ฉันใช้ Playframework ในที่สุดสิ่งที่ฉันต้องการจริงๆคือ a 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อยู่เบื้องหลัง

ขอบคุณล่วงหน้าสำหรับความช่วยเหลือ!

คำตอบ:


148

เคล็ดลับคือก่อนอื่นให้แน่ใจว่าไม่มีฟิวเจอร์สล้มเหลว .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))

ขอบคุณ! .recoverเป็นชิ้นส่วนที่ขาดหายไปสำหรับฉัน
โจ

20
คุณสามารถใช้_.collect{ case Success(x) => x}แทน_.filter(_.isSuccess)การกำจัดTryในประเภทfutureListOfSuccesses.
senia

43
ใน scala 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

ไม่เป็นเช่นนั้น ฉันกำลังทำแผนที่อนาคตไปสู่อนาคตอีกครั้งไม่จำเป็นต้องมีคำสัญญาในการแทรกแซงและจะเป็นการสิ้นเปลือง
Kevin Wright

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

ฉันลองคำตอบของเควินและพบข้อผิดพลาดในเวอร์ชันของ 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())
}

แนวคิดในที่นี้คือคุณกำลังรอให้องค์ประกอบถัดไปในรายการเสร็จสมบูรณ์ (โดยใช้ไวยากรณ์เพื่อความเข้าใจ) และหากองค์ประกอบถัดไปล้มเหลวคุณก็จะกลับไปที่สิ่งที่คุณมีอยู่แล้ว


ฉันไม่ชอบชื่อนี้ แต่ฉันชอบวิธีการทำตรงจากลำดับ
im

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 (ตัวเลือก (_)) กู้คืน {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.