Error handling Scala : Future For Comprehension

20,601

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.

Share:
20,601

Related videos on Youtube

konquestor
Author by

konquestor

Updated on March 18, 2020

Comments

  • konquestor
    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.

    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
    
  • konquestor
    konquestor over 8 years
    Thanks 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
    konquestor over 8 years
    Future.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
    Peter Neyens over 8 years
    Try something like the last code example in my answer.
  • konquestor
    konquestor over 8 years
    Not sure, what you mean, returning InternalServerError causes type mismatch, because the method is expecting Future of Type Response.
  • Peter Neyens
    Peter Neyens over 8 years
    I'm not sure which Response type you mean, but try replacing InternalServerError(errorMsg) with your Response(Nil, Nil, errorMsg).
  • Peter Neyens
    Peter Neyens over 8 years
    Note that you should not create a Response inside a recover or recoverWith in the for comprehension itself, because in that case the Response will be passed to the following callFutureX.
  • konquestor
    konquestor over 8 years
    returning 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
    Peter Neyens over 8 years