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