如果 f 和 g 都失败了,你怎么能让 `first` return 成为 `Failure`?

How can you make `first` return a `Failure` if both f and g fail?

来自官方scala的例子documentation:

def first[T](f: Future[T], g: Future[T]): Future[T] = {
  val p = promise[T]
  f onSuccess {
    case x => p.trySuccess(x)
  }
  g onSuccess {
    case x => p.trySuccess(x)
  }
  p.future
}

Note that in this implementation, if neither f nor g succeeds, then first(f, g) never completes (either with a value or with an exception).

有warning,但是没有对应的解决方法。如果 f 和 g 都失败了,你如何让 first return 变成 Failure

像这样的东西可以完成这项工作,虽然它不是基于与您的示例相同的逻辑(这个解决方案是 错误的 ,请检查 Update)

def first[T](f: Future[T], g: Future[T]): Future[T] = {
  val p = Promise[T]
  p.completeWith(f.fallbackTo(g))
  p.future
}

如果两者都失败,first holds throwable 来自 f

更新(基于@Jatin 的评论): 我的第一个解决方案是错误的,因为如果 f 未来永远不会完成,那么即使 g 完成,first 也永远不会完成。此更新的解决方案使用第一个完成的值非确定性地完成 Promise

def first[T](f: Future[T], g: Future[T]): Future[T] = {
  val p = Promise[T]
  p.completeWith(Future.firstCompletedOf(Seq(f.fallbackTo(g),g.fallbackTo(f))))
  p.future
}

另一个解决方案:您需要在列表中找到第一个成功的。或者如果全部失败则失败。

该解决方案本质上有点有效,很高兴看到纯函数式实现:

def second[T](ls: Seq[Future[T]]): Future[T] = {
  val p = Promise[T]()
  val size = ls.size
  val count = new AtomicInteger(0)

  ls.foreach(x => x onComplete{
    case Success(a) => p.tryCompleteWith(x)
    case Failure(y) =>  if(count.incrementAndGet() == size) p.tryCompleteWith(x)
  })
  p.future
}

下面按预期打印 2

second(List(Future{ Thread.sleep(5000); 1}, Future{Thread.sleep(3000) ;2}, Future{throw new RuntimeException})) onComplete{
  case Success(x) => println(x)
  case Failure(y) => y.printStackTrace()
}