Scala: Başarısız vadeli işlemleri göz ardı ederek [Geleceği] Geleceğe [Liste]


116

Keyfi uzunluktaki bir Vadeli İşlemler listesini Geleceğin Listesine dönüştürmenin bir yolunu arıyorum. Oyun Çerçevesi kullanıyorum, sonuçta, gerçekten istediğim şey a Future[Result], ama işleri daha basit hale getirmek için diyelim ki, bunu Future[List[Int]]yapmanın normal yolu kullanmak olurdu Future.sequence(...)ama bir bükülme var ... Bana verilen liste genellikle yaklaşık 10-20 vadeli işlem ve bu vadeli işlemlerden birinin başarısız olması alışılmadık bir durum değil (harici web hizmeti talepleri yapıyorlar). Birinin başarısız olması durumunda hepsini tekrar denemek yerine, başarılı olanlara ulaşıp geri verebilmek istiyorum.

Örneğin, aşağıdakileri yapmak işe yaramaz

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

Tek istisnayı elde etmek yerine, 1 ve 3'ü oradan çıkarabilmek istiyorum. Kullanmayı denedim Future.fold, ama görünüşe göre bu sadece Future.sequenceperde arkasını arıyor .

Yardım için şimdiden teşekkürler!

Yanıtlar:


147

İşin püf noktası, önce hiçbir gelecek işleminin başarısız olmadığından emin olmak. .recoverburada arkadaşınız, maptüm Future[T]sonuçları Future[Try[T]]], başarılı gelecekler olduğu kesin olan örneklere dönüştürmek için onu birleştirebilirsiniz .

not: Burada Optionveya Eitherburada da kullanabilirsiniz , ancak Tryözellikle istisnaları yakalamak istiyorsanız en temiz yoldur

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

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

Daha sonra Future.sequencesize vermek için eskisi gibi kullanınFuture[List[Try[T]]]

val futureListOfTrys = Future.sequence(listOfFutureTrys)

Ardından filtreleyin:

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

İhtiyacınız olursa belirli arızaları bile ortadan kaldırabilirsiniz:

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

Teşekkürler! .recovergerçekten benim için eksik parçaydı.
Joe

20
İn türünden kurtulmak _.collect{ case Success(x) => x}yerine kullanabilirsiniz . _.filter(_.isSuccess)TryfutureListOfSuccesses
senia

43
Scala 2010'da .recover(x => Failure(x))geçerli değil, .recover({case e => Failure(e)})onun yerine kullanın
FGRibreau

Sanırım gelecekteki sarmalayıcıyı kaçırıyorsunuz: def futureToFutureOf Try [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

öyle değil. Bir Geleceği başka bir geleceğe haritalandırıyorum, araya giren bir Söze gerek yok ve israf olur
Kevin Wright

12

Scala 2.12, Future.transformdaha az kod içeren bir yanıtlayıcıda kendini ödünç veren bir iyileştirmeye sahiptir .

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'in cevabını denedim ve Scala sürümümde (2.11.5) bir sorunla karşılaştım ... Bunu düzelttim ve ilgilenen varsa birkaç ek test yazdım ... işte benim sürümüm>

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

Bu soruyla karşılaştım ve önerebileceğim başka bir çözüm var:

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

Buradaki fikir, katlamanın içinde listedeki bir sonraki öğenin tamamlanmasını beklemenizdir (anlama sözdizimini kullanarak) ve sonraki öğe başarısız olursa, yalnızca sahip olduklarınıza geri dönersiniz.


İsimden hoşlanmıyorum ama doğrudan
impl

1

Gelecekteki sonucu seçenekle kolayca kaydırabilir ve ardından listeyi düzleştirebilirsiniz:

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

Bir başkasının ilk işlevde Bazıları ile bir hatayla karşılaşması durumunda, derleyici hatasını önlemek için ilk işlev şu şekilde yeniden yazılabilir: def futureToFutureOption [T] (f: Future [T]): Future [Option [T]] = f.map (Seçenek (_)). kurtar {case e => None}
Zee

0

Ayrıca başarılı ve başarısız sonuçları farklı listelerde toplayabilirsiniz:

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) }
    }
  }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.