Scala: Daftar [Masa Depan] ke Masa Depan [Daftar] mengabaikan masa depan yang gagal

116

Saya sedang mencari cara untuk mengonversi daftar panjang Kontrak Berjangka yang berubah-ubah menjadi Daftar Masa Depan. Saya menggunakan Playframework, jadi pada akhirnya, yang benar-benar saya inginkan adalah a Future[Result], tetapi untuk mempermudah, anggap saja Future[List[Int]]Cara normal untuk melakukan ini adalah dengan menggunakan Future.sequence(...)tetapi ada twist ... Daftar yang saya berikan biasanya memiliki sekitar 10-20 masa depan di dalamnya, dan tidak jarang salah satu masa depan tersebut gagal (mereka membuat permintaan layanan web eksternal). Alih-alih harus mencoba semuanya jika salah satunya gagal, saya ingin mendapatkan yang berhasil dan mengembalikannya.

Misalnya, melakukan hal berikut tidak berhasil

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

Alih-alih mendapatkan satu-satunya pengecualian, saya ingin dapat menarik 1 dan 3 keluar dari sana. Saya mencoba menggunakanFuture.fold , tetapi ternyata itu hanya panggilanFuture.sequence belakang layar.

Terima kasih sebelumnya atas bantuannya!

Joe
sumber

Jawaban:

147

Triknya adalah pertama-tama memastikan bahwa tidak ada masa depan yang gagal. .recoveradalah teman Anda di sini, Anda dapat menggabungkannya dengan mapuntuk mengubah semua Future[T]hasil menjadi Future[Try[T]]]contoh, yang semuanya pasti akan sukses di masa depan.

catatan: Anda dapat menggunakan Optionatau Eitherjuga di sini, tetapi Trymerupakan cara terbersih jika Anda secara khusus ingin menjebak pengecualian

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

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

Kemudian gunakan Future.sequenceseperti sebelumnya, untuk memberi Anda fileFuture[List[Try[T]]]

val futureListOfTrys = Future.sequence(listOfFutureTrys)

Kemudian filter:

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

Anda bahkan dapat menarik kegagalan tertentu, jika Anda membutuhkannya:

val futureListOfFailures = futureListOfTrys.map(_.filter(_.isFailure))
Kevin Wright
sumber
Terima kasih! .recovermemang bagian yang hilang untukku.
Joe
20
Anda bisa menggunakan _.collect{ case Success(x) => x}alih-alih _.filter(_.isSuccess)menyingkirkan Trydalam jenis futureListOfSuccesses.
senia
43
Dalam scala 2010 .recover(x => Failure(x))tidak valid, gunakan .recover({case e => Failure(e)})sebagai gantinya
FGRibreau
Saya rasa Anda melewatkan pembungkus masa depan: 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
tidak begitu. Saya memetakan Masa Depan ke masa depan lain, janji intervensi tidak diperlukan dan akan sia
Kevin Wright
12

Scala 2.12 memiliki peningkatan Future.transformyang cocok untuk jawaban dengan kode yang lebih sedikit.

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)))
WeiChing 林 煒 清
sumber
11

Saya mencoba jawaban Kevin, dan saya menemukan kesalahan pada versi Scala saya (2.11.5) ... Saya mengoreksinya, dan menulis beberapa tes tambahan jika ada yang tertarik ... ini versi saya>

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)
  }
Chris Bedford
sumber
7

Saya baru saja menemukan pertanyaan ini dan memiliki solusi lain untuk ditawarkan:

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

Idenya di sini adalah bahwa di dalam flip Anda sedang menunggu elemen berikutnya dalam daftar untuk diselesaikan (menggunakan sintaks for -rehension) dan jika yang berikutnya gagal Anda hanya kembali ke apa yang sudah Anda miliki.

Idan Waisman
sumber
Saya tidak suka namanya tapi saya suka cara pembuatannya, langsung dari urutan impl
crak
1

Anda dapat dengan mudah membungkus hasil mendatang dengan opsi dan kemudian meratakan daftar:

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
Amir Hossein Javan
sumber
Untuk berjaga-jaga jika orang lain menemukan kesalahan dengan Some di fungsi pertama, fungsi pertama dapat ditulis ulang seperti itu untuk mencegah kesalahan kompiler: def futureToFutureOption [T] (f: Future [T]): Future [Option [T]] = f.map (Option (_)). recover {case e => None}
Zee
0

Anda juga dapat mengumpulkan hasil yang berhasil dan tidak berhasil dalam daftar yang berbeda:

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) }
    }
  }
}
Evgeniy Lyutikov
sumber