Await a future, receive an either
Asked Answered
T

3

35

I'd like to await a scala future that may have failed. If I use Await.result the exception will be thrown. Instead, if I have f: Future[String] I would like a method Await.resultOpt(f): Option[String] or Await.resultEither(f): Either[String].

I could get this by using scala.util.control.Exception.catching or I could f map (Right(_)) recover { case t: Throwable => Left(t) }, but there must be a more straightforward way.

Taryntaryne answered 28/1, 2014 at 21:31 Comment(1)
Try / Success / Failure are a lot nicer to deal with than Either[Throwable, Value]. IMO...Mount
M
68

You could use Await.ready which simply blocks until the Future has either succeeded or failed, then returns a reference back to that Future.

From there, you would probably want to get the Future's value, which is an Option[Try[T]]. Due to the Await.ready call, it should be safe to assume that the value is a Some. Then it's just a matter of mapping between a Try[T] and an Either[Throwable, T].

The short version:

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)
}
Maintain answered 28/1, 2014 at 21:42 Comment(5)
I think this is better solution: Await.ready(f, Duration.Inf).onComplete(result => { result match { case Success(t) => Right(t) case Failure(e) => Left(e) } }) calling get on Option is generally a bad ideaAnissa
@Anissa in most cases I would agree, but in this case it's perfectly fine; if Await.ready doesn't throw, the value will always be a Some. One is free to add the extra check for code quality reasons, but it should be technically unnecessary here.Maintain
@Maintain - why did you use Await#ready instead of Await#result?Telluride
@KevinMeredith because the OP specifically pointed out the issues with Await.ready, and asked for an alternative.Maintain
@KevinMeredith (whoops, meant "issues with Await.result")Maintain
B
3

The shorter version, just to promote the 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)

It has a slight advantage in being a one-liner.

But of course, after adding a .toEither extension method, you don't care how many lines it took.

Behave answered 29/1, 2014 at 0:41 Comment(2)
This won't work if the Future hasn't finished yet. f.value will be None, so calling f.value.get will throw an Exception.Maintain
@Maintain I was just demonstrating Either.cond at res2. Obviously, one must wait for it. Edit: oh, you're that Dylan. Yes, your match is fine, this is a one-liner, not necessarily perspicacious. Edit again: perspicacity doesn't matter either if you pimped it.Behave
F
1

You could start to make your own type utils and do something like so

  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))
  }

I separated it out for a Try to also have a .fold :).

Friedrich answered 2/3, 2018 at 15:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.