【发布时间】:2020-11-05 05:55:35
【问题描述】:
我有两个 scala 期货。我想在两者都完成后执行一项操作,无论它们是否成功完成。 (此外,我希望能够在那时检查这些结果。)
在 Javascript 中,这是Promise.allSettled。
Scala 是否提供了一种简单的方法来做到这一点?
最后一点,如果重要的话:我想在 JRuby 应用程序中执行此操作。
【问题讨论】:
-
你看
Future.sequence了吗?
我有两个 scala 期货。我想在两者都完成后执行一项操作,无论它们是否成功完成。 (此外,我希望能够在那时检查这些结果。)
在 Javascript 中,这是Promise.allSettled。
Scala 是否提供了一种简单的方法来做到这一点?
最后一点,如果重要的话:我想在 JRuby 应用程序中执行此操作。
【问题讨论】:
Future.sequence了吗?
您可以使用transform 方法创建一个Future,它将始终成功并将结果或错误作为Try 对象返回。
def toTry[A](future: Future[A])(implicit ec: ExecutionContext): Future[Try[A]] =
future.transform(x => Success(x))
要将两个 Future 合二为一,可以使用zip:
def settle2[A, B](fa: Future[A], fb: Future[B])(implicit ec: ExecutionContext)
: Future[(Try[A], Try[B])] =
toTry(fa).zip(toTry(fb))
如果你想通过这种方式组合任意数量的Futures,可以使用Future.traverse:
def allSettled[A](futures: List[Future[A]])(implicit ec: ExecutionContext)
: Future[List[Try[A]]] =
Future.traverse(futures)(toTry(_))
【讨论】:
Future 和 Try 过于复杂/工程化
Promise.allSettled 产生了一个与Scala 的Try 类型同构的对象数组。我编写的 allSettled 方法与 JavaScript 中惯用的 Scala 中的 Promise.allSettled 一样接近。
通常在这种情况下,我们使用 Future.sequence 将 Future 的集合转换为一个 Future 以便您可以在其上进行映射,但 Scala 会短路失败的 Future 并且此后不会等待任何事情(Scala 认为失败是所有人的失败),这不适合你的情况。
在这种情况下,您需要将失败的映射到成功,然后执行序列,例如
val settledFuture = Future.sequence(List(future1, future2, ...).map(_.recoverWith { case _ => Future.unit }))
settledFuture.map(//Here it is all settled)
编辑
由于需要保留结果,我们不映射到Future.unit,而是将实际结果映射到另一层Try:
val settledFuture = Future.sequence(
List(Future(1), Future(throw new Exception))
.map(_.map(Success(_)).recover(Failure(_)))
)
settledFuture.map(println(_))
//Output: List(Success(1), Failure(java.lang.Exception))
EDIT2
可以用transform进一步简化:
Future.sequence(listOfFutures.map(_.transform(Success(_))))
【讨论】:
Any类型的结果,因为这通常是future结果类型和Unit的最小上限,如果future返回Unit,你不知道原来的future是成功还是失败.最重要的是,OP 提到他有两个期货,并且没有理由假设它们属于同一类型,因此您再次失去了类型安全性。此外,Future.sequence(foo.map(bar)) 更好地表示为Future.traverse(foo)(bar)。
Future.sequence(foo.map(bar)) 反模式和 d) 不能组合 Futures不同的类型,同时保留它们的类型(OP 明确询问了两个 Futures,而不是任意数字)
Promise.allSettled 的引用中,它返回了所有结果的集合,这正是我的示例所做的。
Throwable 的子类型作为Future 的成功结果是一个反模式是无关紧要的;像这样的通用代码不应该对此做出任何假设。非密封类型(如Any)上的模式匹配是另一个糟糕的想法,因为编译器无法检查详尽性并且因为它不适用于泛型(例如,无法区分List[Int] 和List[String] )。
也许您可以使用并发计数器来跟踪已完成的 Futures 的数量,然后在所有 Futures 完成后完成 Promise
def allSettled[T](futures: List[Future[T]]): Future[List[Future[T]]] = {
val p = Promise[List[Future[T]]]()
val length = futures.length
val completedCount = new AtomicInteger(0)
futures foreach {
_.onComplete { _ =>
if (completedCount.incrementAndGet == length) p.trySuccess(futures)
}
}
p.future
}
val futures = List(
Future(-11),
Future(throw new Exception("boom")),
Future(42)
)
allSettled(futures).andThen(println(_))
// Success(List(Future(Success(-11)), Future(Failure(java.lang.Exception: boom)), Future(Success(42))))
【讨论】: