Lately I have seen a few developers consistently use a Try inside of a Future in order to make error handling easier. Here I will investigate if this has any merits or whether a Future on it’s own offers enough error handling.
If you look at the following code there is nothing that a Future can’t supply but a Try can:
[scala]
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.{Await, Future, Awaitable}
import scala.concurrent.duration._
import scala.util.{Try, Success, Failure}
object Main extends App {
// Happy Future
val happyFuture = Future {
42
}
// Bleak future
val bleakFuture = Future {
throw new Exception("Mass extinction!")
}
// We would want to wrap the result into a hypothetical http response
case class Response(code: Int, body: String)
// This is the handler we will use
def handle[T](future: Future[T]): Future[Response] = {
future.map {
case answer: Int => Response(200, answer.toString)
} recover {
case t: Throwable => Response(500, "Uh oh!")
}
}
{
val result = Await.result(handle(happyFuture), 1 second)
println(result)
}
{
val result = Await.result(handle(bleakFuture), 1 second)
println(result)
}
}
[/scala]
After giving it some thought the only situation where I could imagine Try being useful in conjunction with Future is when awaiting a Future but not wanting to deal with error situations yet. The times I would be awaiting a future are very few in practice though. But when needed something like this migth do:
[scala]
object TryAwait {
def result[T](awaitable: Awaitable[T], atMost: Duration): Try[T] = {
Try {
Await.result(awaitable, atMost)
}
}
}
[/scala]
If you do feel that using Trys inside of Futures adds value to your codebase please let me know.
by Dirk Louwers
Thanks for the tip, helped here!
I’ve ended up using Future[Try[_]] quite often, and it’s almost always when I need to do Future.sequence on a collection of futures, and I don’t want the failure of one future to fail the entire thing.
So imagine I’m sending an HTTP request to 20 servers, and I want to enumerate their responses or errors. If one of them times out, I still want to save the results, so I end up with something like…
Future[List[Try[_]]
Usually the quickest way to accomplish this is just…
Future.sequence(futures.map(_.map(Success).recover(Failure(_)))
although maybe there’s some monad transformer approach that would be cleaner.
Hi Chad,
I guess that it’s very situational. Since you are stating that you do not want the rest to fail it seems to mean that these future results are optional. So why not something like this:
future.map(Some(_)).recover { err =>
log.warning(s”Something went wrong processing an item: $err”)
None
}
All dependent on requirements of course. If, for instance, the handling of the error depends on other errors and/or successful futures this wouldn’t work.