Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Error handling Scala : Future For Comprehension

Tags:

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.

  1. First call to db to fetch some data
  2. Use the data in first call to fetch other data from db
  3. 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 
like image 245
konquestor Avatar asked Aug 30 '15 03:08

konquestor


People also ask

How does Scala handle exceptions in future?

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.

What kind of error handling does Scala have?

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.

What is a future in Scala?

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.

How do you catch errors in Scala?

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.


1 Answers

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) } 
like image 187
Peter Neyens Avatar answered Sep 24 '22 03:09

Peter Neyens