斯卡拉期货-如何从两个期货中获得结果或失败

时间:2019-04-11 07:11:19

标签: scala future

我正在使用for并行运行2个期货。我想知道在所有情况下哪个成功,哪个失败(所有都应该运行直到结果为失败或失败状态)。目前,我只能检索合并的成功结果

我从这里进行了检查,但这还不够,因为当一个失败时我没有获得成功状态,而在failure in Scala future's for comprehension都失败的情况下我都没有获得失败状态

case class TaggedException(context:String, val throwable: Throwable) extends Exception(throwable.getMessage)

val f1 = Future {...}.recoverWith {case e:Throwable => Future.Failed(new TaggedException("first one failed", e))}
val f2 = Future {...}.recoverWith {case e: Throwable => Future.Failed(new TaggedException("second one failed", e))}

val combinedResult = for {
  r1 <- f1
  r2 <- f2
} yield (r1,r2)

combinedResult.onFailure {
case e : TaggedException => ... // if both fail I only get the first line in the for
// in case where single fails I only know fail status without the success of the second
}

我正在努力避免这种混乱:

var countCompleted = 0 ... or some other atomic way to count 
f1 onComplete {
  case Success(value) => {
    ... countCompleted increment ... 
    // handle success
    if both completed {
       // handle returning a status
    }
  }
  case Failure(error) => {
    ... countCompleted increment ... 
    // handle failure
    if both completed {
       // handle returning a status
    }
  }
}

f2 onComplete {
  case Success(value) => {
    ... countCompleted increment ... 
    // handle success
    if both completed {
       // handle returning a status
    }
  }
  case Failure(error) => {
    ... countCompleted increment ... 
    // handle failure
    if both completed {
       // handle returning a status
    }
  }
}

编辑:另一个版本-这是有效的方法吗?

def toFutureTry[A](future: Future[A]):Future[Try[A]] = future.map(Success(_)).recover {case t: Throwable => Failure(t)}

    val fa: Future[Try[Blah]] = toFutureTry(f1)
    val fb: Future[Try[Foo]] = toFutureTry(f2)

    val combinedRes = for {
      ra <- fa
      rb <- fb
    } yield (ra,rb)

    combinedRes.onComplete {
      case Success(successRes: (Try[Blah], Try[Foo])) => // all of these cases are success or fails
      case Failure(f: Throwable) => // i think it is unused right?
    }

4 个答案:

答案 0 :(得分:1)

flatMap上使用Future[A]将无济于事,因为它总是会在其中一个确实要累积错误的情况下使其中一个发生的首次故障短路。

使用Future.traverse的解决方案可以在任意多个Future[A]实例上工作:

val f1 = Future.failed[Int](new Exception("42")).recoverWith {
  case e: Throwable => Future.failed(TaggedException("first one failed", e))
}

val f2 = Future(42).recoverWith {
  case e: Throwable =>
    Future.failed(TaggedException("second one failed", e))
}

val res: Future[List[Either[Throwable, Int]]] = 
  Future
   .traverse(List(f1, f2)) {
      eventualInt => eventualInt
       .map(i => Right(i))
       .recover { case err => Left(err) }
   }

res.onComplete {
  case Failure(exception) =>
    println(exception)
  case Success(value) =>
    value.foreach {
      case Right(int) => println(s"Received num: $int")
      case Left(err) => println(s"Oh no, err: $err")
    }
}

Await.result(res, Duration.Inf)

我们还可以使用Validated类型的猫来帮忙:

import cats.data.Validated.{Invalid, Valid}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.Duration
import scala.concurrent.{Await, Future}
import cats.implicits._
import scala.util.{Failure, Success}

def main(args: Array[String]): Unit = {
  case class TaggedException(context: String, throwable: Throwable)
    extends Exception(throwable.getMessage)

  val f1 = Future.failed[Int](new Exception("42")).recoverWith {
    case e: Throwable => Future.failed(TaggedException("first one failed", e))
  }

  val f2 = Future(42).recoverWith {
    case e: Throwable => Future.failed(TaggedException("second one failed", e))
  }

  val res: Future[List[Validated[Throwable, Int]]] = 
    List(f1, f2)
     .traverse(eventualInt => eventualInt
                       .map(i => Valid(i))
                       .recover { case err => Invalid(err) })

  res.onComplete {
    case Failure(exception) =>
      println(exception)
    case Success(value) =>
      value.foreach {
        case Valid(int) => println(s"Received num: $int")
        case Invalid(err) => println(s"Oh no, err: $err")
      }
  }

  Await.result(res, Duration.Inf)
}

将产生产量:

Oh no, err: TaggedException$3: 42
Received num: 42

答案 1 :(得分:1)

您可以像这样组合transformzip

val combinedResult: Future[(Try[T], Try[T])] =
  f1.transform(Success(_)).zip(f2.transform(Success(_)))

那么您可以做:

combinedResult map {
  case (Success(v1), Success(v2)) =>
  case (Success(v1), Failure(f2)) =>
  case (Failure(f1), Success(v2)) =>
  case (Failure(f1), Failure(f2)) =>
}

答案 2 :(得分:0)

有了理解,只要一行失败,代码就会在此停止,并抛出任何异常。如果r1 <- f1抛出一个Throwable,r2 <- f2将永远不会被击中。

我个人将它们都放在Either[Throwable, whatever]中,而不是将每个.recoverWith放在Future.Failed(...)中。这样,您不仅可以执行onFailure的操作,还可以使用获得的任何Left的值进行操作,还可以使用获得的任何Right的值进行其他操作。或者,您可以使用Try/Success/Failure ...取决于您要如何处理错误。

我不知道您的特定用例,但是如果您想分别处理每个成功或失败的情况,则可以执行以下操作:

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.util.{Failure, Success, Try}

val f1 = Future(Try(1 / 1))
val f2 = Future(Try(1 / 0))

// for-comprehensions won't fall over when reading a Failure
// as failures don't extend Throwable
val combinedResult = for {
  r1 <- f1 // Success(1)
  r2 <- f2 // Failure(java.lang.ArithmeticException: / by zero)
} yield List(r1,r2)

combinedResult.map { // get inside Future
  f =>
    f.map { // get inside List
      case Success(a) => // do something with success
      case Failure(e: IndexOutOfBoundsException) => // do something with failure
      case Failure(e: ArithmeticException) => // do something with failure
      case Failure(e) => // do something with failure
    }
}

我个人不喜欢使用onComplete;我更喜欢通过映射内部将数据保留在Future中。不过那只是个人喜好。

答案 3 :(得分:0)

~~首先,您不是并行运行期货(理解将依次运行)。~~

更新,上述内容不正确,如评论中所述。我想念期货是在理解之外创建的。

其次,正如您所注意到的,如果其中任何一种期货失败,则另一种期货将丢失。

要解决以下事实:如果其中一项期货失败,另一项期货的结果将丢失,则可以将未来价值“提升”到Try

val lifted1: Future[Try[Foo]] = f1.transform(Success(_))
val lifted2: Future[Try[Bar]] = f1.transform(Success(_))

现在,您可以执行以下操作:

 (lifted1 zip lifted2).map { 
   case (Success(foo), Success(bar)) => // both succeeded!
   case (Success(foo), Failure(t)) => // foo succeeded, bar failed with t
   case (Failure(t), Success(bar)) => // guess what!
   case (Failure(t1), Failure(t2)) => // oops
 }

如果您发现自己经常这样做,可能会发现它很有用,通过lift操作“拉皮条”您的期货:

 object FutureSyntax { 
   implicit class FutureOps[A](val fu: Future[A]) extends AnyVal {
      def liftToTry: Future[Try[A]] = fu.transform(Success(_))
   }
 }

所以,现在,如果您import FutureSyntax._,则以上内容可以写为

 (f1.liftToTry zip f2.liftToTry).map { 
    case (Success(foo), Success(bar)) => ... 
    ...
 }

您也可以带着理解来写同样的东西,只是更加冗长。在这种情况下,理解不是我的选择:当处理一系列期货时,它们是很好的,其中期货的顺序取决于较早的期货的结果。对于处理独立的期货,通常使用zipsequence是更好的选择。