等待一个Future,接收一个Either

34
我希望等待一个可能失败的 Scala Future。如果使用 Await.result,异常将被抛出。相反,如果我有 f: Future[String],我想要一个方法 Await.resultOpt(f): Option[String] 或 Await.resultEither(f): Either[String]。
我可以使用 scala.util.control.Exception.catching 来实现这一点,或者我可以使用 f map (Right(_)) recover { case t: Throwable => Left(t) },但必须有更直接的方法。

2
尝试 / 成功 / 失败 比 Either[Throwable, Value] 更加友好。在我看来... - Randall Schulz
3个回答

66
您可以使用Await.ready方法,它会一直阻塞直到Future成功或失败,然后返回一个对该Future的引用。接下来,您可能想要获取Future的value,它是一个Option[Try[T]]类型。由于Await.ready的调用,我们可以放心地假设valueSome类型。然后就只需要在Try[T]Either[Throwable, T]之间进行映射即可。
简而言之:
val f: Future[T] = ...

val result: Try[T] = Await.ready(f, Duration.Inf).value.get

val resultEither = result match {
  case Success(t) => Right(t)
  case Failure(e) => Left(e)
}

2
我认为这是更好的解决方案:Await.ready(f, Duration.Inf).onComplete(result => { result match { case Success(t) => Right(t) case Failure(e) => Left(e) } })通常在Option上调用get是一个不好的主意。 - Klapsa2503
1
在大多数情况下,我会同意@Klapsa2503的观点,但在这种情况下,它是完全可以的;如果Await.ready没有抛出异常,那么value将始终是Some。出于代码质量的原因,人们可以添加额外的检查,但在这里技术上应该是不必要的。 - Dylan
@Dylan - 你为什么使用Await#ready而不是Await#result - Kevin Meredith
1
@KevinMeredith 因为原帖特别指出了Await.ready的问题,并要求提供替代方案。 - Dylan
2
@KevinMeredith(哎呀,我的意思是“使用Await.result时出现问题”) - Dylan

3

简短的版本,仅用于宣传API:

scala> val f = Future(7)
f: scala.concurrent.Future[Int] = scala.concurrent.impl.Promise$DefaultPromise@13965637

scala> f.value.get
res0: scala.util.Try[Int] = Success(7)

scala> import scala.util._
import scala.util._

scala> Either.cond(res0.isSuccess, res0.get, res0.failed.get)
res2: scala.util.Either[Throwable,Int] = Right(7)

scala> val f = Future[Int](???)
f: scala.concurrent.Future[Int] = scala.concurrent.impl.Promise$DefaultPromise@64c4c1

scala> val v = f.value.get
v: scala.util.Try[Int] = Failure(java.util.concurrent.ExecutionException: Boxed Error)

scala> Either.cond(v.isSuccess, v.get, v.failed.get)
res4: scala.util.Either[Throwable,Int] = Left(java.util.concurrent.ExecutionException: Boxed Error)

这个代码片段以一行为优势。

但是,当添加了.toEither扩展方法后,您不必关心它需要多少行。


如果Future还没有完成,这将不起作用。f.value将为None,因此调用f.value.get将抛出异常。 - Dylan
@Dylan,我只是在res2中演示Either.cond。显然,必须等待它。编辑:哦,你就是那个Dylan。是的,你的匹配很好,这是一行代码,不一定要有洞察力。再次编辑:如果你把它弄得花哨起来,洞察力也无所谓。 - som-snytt

1
你可以开始制作自己的类型工具,然后像这样做。
  trait RichTypes {

    import scala.util.{Try, Success, Failure}
    import scala.concurrent.{Await, Future}
    import scala.concurrent.duration.Duration

    implicit class RichFuture[T](f: Future[T]) {
      def awaitResult(d: Duration): Either[Throwable, T] = {
        Try(Await.result(f, d)).toEither
      }
    }

    implicit class RichTry[T](tri: Try[T]) {
      def toEither(): Either[Throwable, T] = {
        tri.fold[Either[Throwable, T]](Left(_), Right(_))
      }
    }
  }

  object Example
    extends App
      with RichTypes {

    import scala.concurrent.Future
    import scala.concurrent.duration._

    val succ = Future.successful("hi").awaitResult(5.seconds)
    val fail = Future.failed(new Exception("x")).awaitResult(5.seconds)

    println(succ) // Right(hi)
    println(fail) // Left(Exception(x))
  }

我将其分离出来,以便 Try 也具有 .fold :).

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接