I want to do error handling in my play scala web application.
My application talks to the data base to fetch some rows, it follows following flow.
Below is my pseudocode.
def getResponse(name: String) (implicit ctxt: ExecutionContext): Future[Response] = { for { future1 <- callFuture1(name) future2 <- callFuture2(future1.data) future3 <- callFuture3(future1.data, future2.data) } yield future3 }
Every method in the comprehension above returns a future, the signature of these methods are as below.
private def callFuture1(name: String) (implicit ctxt: ExecutionContext): Future[SomeType1] {...} private def callFuture2(keywords: List[String]) (implicit ctxt: ExecutionContext): Future[SomeType2] {...} private def callFuture3(data: List[SomeType3], counts: List[Int]) (implicit ctxt: ExecutionContext): Future[Response] {...}
How shall I do error/failure handling, in the following situation
--edit--
I am trying to return an appropriate Error Response from getResponse() method, when either of the callFuture fails and not proceed to subsequent futureCalls.
I tried the following, based on Peter Neyens answer, but gave me an runtime error..
def getResponse(name: String) (implicit ctxt: ExecutionContext): Future[Response] = { for { future1 <- callFuture1(name) recoverWith { case e:Exception => return Future{Response(Nil,Nil,e.getMessage)} } future2 <- callFuture2(future1.data) future3 <- callFuture3(future1.data, future2.data) } yield future3 }
Runtime error i get
ERROR] [08/31/2015 02:09:45.011] [play-akka.actor.default-dispatcher-3] [ActorSystem(play)] Uncaught error from thread [play-akka.actor.default-dispatcher-3] (scala.runtime.NonLocalReturnControl) [error] a.a.ActorSystemImpl - Uncaught error from thread [play-akka.actor.default-dispatcher-3] scala.runtime.NonLocalReturnControl: null
You could use the Future. recoverWith function, to customize the exception if the Future failed. Note that you could also define your own exceptions to use instead of Exception , if you want to add more information than just an error message.
Exception handling is the mechanism to respond to the occurrence of an exception. Exceptions can be checked or unchecked. Scala only allows unchecked exceptions, though. This means that, at compile-time, we won't be able to know if a method is throwing an exception we are not handling.
A Future is a placeholder object for a value that may not yet exist. Generally, the value of the Future is supplied concurrently and can subsequently be used. Composing concurrent tasks in this way tends to result in faster, asynchronous, non-blocking parallel code.
Like Java, Scala has a try/catch/finally construct to let you catch and manage exceptions. The main difference is that for consistency, Scala uses the same syntax that match expressions use: case statements to match the different possible exceptions that can occur.
You could use the Future.recoverWith
function, to customize the exception if the Future
failed.
val failed = Future.failed(new Exception("boom")) failed recoverWith { case e: Exception => Future.failed(new Exception("A prettier error message", e) }
This will result in a slightly uglier for comprehension :
for { future1 <- callFuture1(name) recoverWith { case npe: NullPointerException => Future.failed(new Exception("how did this happen in Scala ?", npe)) case e: IllegalArgumentException => Future.failed(new Exception("better watch what you give me", e)) case t: Throwable => Future.failed(new Exception("pretty message A", t)) } future2 <- callFuture2(future1.data) recoverWith { case e: Exception => Future.failed(new Exception("pretty message B", e)) } future3 <- callFuture3(future1.data, future2.data) recoverWith { case e: Exception => Future.failed(new Exception("pretty message C", e)) } } yield future3
Note that you could also define your own exceptions to use instead of Exception
, if you want to add more information than just an error message.
If you don't want fine grained control to set a different error message depending on the Throwable
in the failed Future
(like with callFuture1
), you could enrich Future
using an implicit class to set a custom error message somewhat simpler:
implicit class ErrorMessageFuture[A](val future: Future[A]) extends AnyVal { def errorMsg(error: String): Future[A] = future.recoverWith { case t: Throwable => Future.failed(new Exception(error, t)) } }
Which you could use like :
for { future1 <- callFuture1(name) errorMsg "pretty A" future2 <- callFuture2(future1.data) errorMsg "pretty B" future3 <- callFuture3(future1.data, future2.data) errorMsg "pretty C" } yield future3
In both cases, using errorMsg
or recoverWith
directly, you still rely on Future
, so if a Future
fails the following Futures
will not be executed and you can directly use the error message inside the failed Future
.
You didn't specify how you would like to handle the error messages. If for example you want to use the error message to create a different Response
you could use recoverWith
or recover
.
future3 recover { case e: Exception => val errorMsg = e.getMessage InternalServerError(errorMsg) }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With