web-dev-qa-db-fra.com

Gestion des erreurs Scala: Future For Comprehension

Je veux gérer les erreurs dans mon jeu scala application web.

Mon application parle à la base de données pour récupérer quelques lignes, elle suit le flux suivant.

  1. Premier appel à db pour récupérer des données
  2. Utilisez les données du premier appel pour récupérer d'autres données de la base de données
  3. Formez une réponse en utilisant les données reçues des deux derniers appels db.

Ci-dessous est mon 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
  }

Chaque méthode dans la compréhension ci-dessus renvoie un avenir, la signature de ces méthodes est comme ci-dessous.

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] {...}

Comment dois-je faire la gestion des erreurs/échecs, dans la situation suivante

  • Lorsque callFuture1 ne parvient pas à récupérer les données de la base de données. Je souhaite renvoyer une réponse d'erreur appropriée avec un message d'erreur. Puisque callFuture2 n'est exécuté qu'après callFuture1. Je ne veux pas exécuter callFuture2 si callFuture1 a échoué/a commis une erreur et souhaite renvoyer un message d'erreur immédiatement. (Même chose pour callFuture2 et callFuture3)

--Éditer--

J'essaie de renvoyer une réponse d'erreur appropriée de la méthode getResponse (), lorsque l'un des callFuture échoue et ne passe pas aux futurs appels futurs.

J'ai essayé ce qui suit, basé sur la réponse de Peter Neyens, mais m'a donné une erreur d'exécution ..

 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
  }

Erreur d'exécution que j'obtiens

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
19
konquestor

Vous pouvez utiliser le Future.recoverWith , pour personnaliser l'exception en cas d'échec de Future.

val failed = Future.failed(new Exception("boom"))
failed recoverWith {
  case e: Exception => Future.failed(new Exception("A prettier error message", e)
}

Cela se traduira par un peu plus laid pour la compréhension:

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

Notez que vous pouvez également définir vos propres exceptions à utiliser au lieu de Exception, si vous souhaitez ajouter plus d'informations qu'un simple message d'erreur.

Si vous ne voulez pas qu'un contrôle à grain fin définisse un message d'erreur différent en fonction de Throwable dans l'échec Future (comme avec callFuture1), vous pouvez enrichir Future en utilisant une classe implicite pour définir un message d'erreur personnalisé un peu plus simplement:

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))
  }
}

Que vous pourriez utiliser comme:

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

Dans les deux cas, en utilisant errorMsg ou recoverWith directement, vous comptez toujours sur Future, donc si un Future échoue, le Futures suivant ne pas être exécuté et vous pouvez directement utiliser le message d'erreur dans le Future.

Vous n'avez pas spécifié comment vous souhaitez gérer les messages d'erreur. Si, par exemple, vous souhaitez utiliser le message d'erreur pour créer un autre Response, vous pouvez utiliser recoverWith ou recover.

future3 recover { case e: Exception =>
  val errorMsg = e.getMessage
  InternalServerError(errorMsg)
}
27
Peter Neyens

Dites future1, future2 Et future3 Lèvent Throwable exceptions nommées Future1Exception, Future2Exception Et Future3Exception , respectivement. Vous pouvez ensuite renvoyer l'erreur appropriée Response à partir de la méthode getResponse() comme suit:

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(...)
  }
}

Selon la documentation Future.recover

Crée un nouvel avenir qui gérera tout objet jetable correspondant que cet avenir pourrait contenir.

3
Mario Galic