可能失败的多个期货 - 返回成功和失败?

Multiple futures that may fail - returning both successes and failures?

我有一种情况需要运行并行操作。

所有操作都具有相同的 return 值(比如 Seq[String])。

有可能部分操作失败,而其他操作成功 return 结果。

我想 return 成功的结果和发生的任何异常,所以我可以记录它们以进行调试。

在我编写自己的 class 之前,是否有内置方法或通过任何库 (cats/scalaz) 的简单方法来执行此操作?

我想在自己的未来做每个操作,然后检查每个未来,然后 returning 一个 Seq[String] -> Seq[Throwable] 的元组,其中左值是成功的结果(展平/组合)和右边是发生的所有异常的列表。

有没有更好的方法?

听起来像是 Try 习语的一个很好的用例(它基本上类似于 Either monad)。

来自 doc 的用法示例:

import scala.util.{Success, Failure}

val f: Future[List[String]] = Future {
  session.getRecentPosts
}

f onComplete {
  case Success(posts) => for (post <- posts) println(post)
  case Failure(t) => println("An error has occurred: " + t.getMessage)
}

它实际上比您要求的要多一点,因为它是完全异步的。它适合您的用例吗?

正在给您的 Future returns 和 Option[Try[T]] 打电话 value。如果 Future 尚未完成,则 OptionNone。如果它已经完成,那么它很容易解包和处理。

if (myFutr.isCompleted)
  myFutr.value.map(_.fold( err: Throwable  => //log the error
                         , ss: Seq[String] => //process results
                         ))
else
 // do something else, come back later

使用您在评论中提到的 Await.ready,通常会失去使用期货的大部分好处。相反,您可以使用普通的 Future 组合器来做到这一点。让我们做一个更通用的版本,它适用于任何 return 类型;可以轻松添加扁平化 Seq[String]s。

def successesAndFailures[T](futures: Seq[Future[T]]): Future[(Seq[T], Seq[Throwable])] = {
  // first, promote all futures to Either without failures
  val eitherFutures: Seq[Future[Either[Throwable, T]]] = 
    futures.map(_.transform(x => Success(x.toEither)))
  // then sequence to flip Future and Seq
  val futureEithers: Future[Seq[Either[Throwable, T]]] = 
    Future.sequence(eitherFutures)
  // finally, Seq of Eithers can be separated into Seqs of Lefts and Rights
  futureEithers.map { seqOfEithers =>
    val (lefts, rights) = seqOfEithers.partition(_.isLeft)
    val failures = lefts.map(_.left.get)
    val successes = rights.map(_.right.get)
    (successes, failures)
  }
}

Scalaz and Cats have separate to simplify the last step.

类型可以由编译器推断,显示它们只是为了帮助您了解逻辑。

我会这样做:

import scala.concurrent.{Future, ExecutionContext}
import scala.util.Success

def eitherify[A](f: Future[A])(implicit ec: ExecutionContext): Future[Either[Throwable, A]] = f.transform(tryResult => Success(tryResult.toEither))

def eitherifyF[A, B](f: A => Future[B])(implicit ec: ExecutionContext): A => Future[Either[Throwable, B]] = { a => eitherify(f(a)) }

// here we need some "cats" magic for `traverse` and `separate`
// instead of `traverse` you can use standard `Future.sequence`
// there is no analogue for `separate` in the standard library

import cats.implicits._

def myProgram[A, B](values: List[A], asyncF: A => Future[B])(implicit ec: ExecutionContext): Future[(List[Throwable], List[B])] = {
  val appliedTransformations: Future[List[Either[Throwable, B]]] = values.traverse(eitherifyF(asyncF))
  appliedTransformations.map(_.separate)
}