Error handling Scala : Future For Comprehension
Solution 1
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)
}
Solution 2
Say future1
, future2
and future3
throw Throwable
exceptions named Future1Exception
, Future2Exception
and Future3Exception
, respectively. Then you can return appropriate error Response
from getResponse()
method as follows:
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).recover {
case e: Future1Exception =>
// build appropriate Response(...)
case e: Future2Exception =>
// build appropriate Response(...)
case e: Future3Exception =>
// build appropriate Response(...)
}
}
According to documentation Future.recover
Creates a new future that will handle any matching throwable that this future might contain.
Related videos on Youtube
konquestor
Updated on March 18, 2020Comments
-
konquestor about 4 years
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.
- First call to db to fetch some data
- Use the data in first call to fetch other data from db
- Form a response using the data received from last two db calls.
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
- When callFuture1 fails to fetch data from database. I want to return a appropriate error response with error message. Since callFuture2 only gets executed after callFuture1. I dont want to execute callFuture2 if callFuture1 has failed/erred and would want to return error message immediately. (Same thing for callFuture2 and callFuture3)
--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
-
konquestor over 8 yearsThanks for that answer. it was helpful. i am still not sure if this solves my problem. 1) Using the approach 1, (without using implicit class), when i recoverwith Future.failed(new Exception()), an exception is thrown and my execution does not proceed. I am trying to return an appropriate ResponseMessage when any of the futurecall fails, without proceeding to subsequent futurecalls( i shall update the question to make it more clear)
-
konquestor over 8 yearsFuture.fail(new Exception(...)) causes the execution to throw an exception. Instead i want to Return an appropriate error message when something fails/errors in futurecall.
-
Peter Neyens over 8 yearsTry something like the last code example in my answer.
-
konquestor over 8 yearsNot sure, what you mean, returning InternalServerError causes type mismatch, because the method is expecting Future of Type Response.
-
Peter Neyens over 8 yearsI'm not sure which
Response
type you mean, but try replacingInternalServerError(errorMsg)
with yourResponse(Nil, Nil, errorMsg)
. -
Peter Neyens over 8 yearsNote that you should not create a
Response
inside arecover
orrecoverWith
in the for comprehension itself, because in that case theResponse
will be passed to the followingcallFutureX
. -
konquestor over 8 yearsreturning Future{IntenralServor(errorMsg)} instead of Future{Response(Nil, Nil, errorMessage)} gives types mismatch compilation error( which makes sense). The getResponse method is expecting return type to be Future[Response] and i am not trying to return Future[InternalServerError]
-
Peter Neyens over 8 yearsLet us continue this discussion in chat.