Обработка ошибок Scala: будущее для понимания
Я хочу выполнить обработку ошибок в своем веб-приложении play scala.
Мое приложение обращается к базе данных для извлечения некоторых строк, это следует за потоком.
- Первый вызов db для получения некоторых данных
- Используйте данные в первом вызове для извлечения других данных из db
- Введите ответ, используя данные, полученные из последних двух вызовов db.
Ниже мой псевдокод.
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
}
Каждый метод, описанный выше, возвращает будущее, подпись этих методов приведена ниже.
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] {...}
Как мне сделать обработку ошибок/сбоев, в следующей ситуации
- Когда callFuture1 не может получить данные из базы данных. я хочу вернуться
соответствующий ответ об ошибке с сообщением об ошибке. Поскольку callFuture2
только выполняется после вызоваFuture1. Я не хочу выполнять
callFuture2, если callFuture1 не удалось/ошибочно и захочет вернуться
сообщение об ошибке немедленно. (То же самое для callFuture2 и
callFuture3)
- редактировать -
Я пытаюсь вернуть соответствующий метод Error Response из метода getResponse(), когда любой из вызововFuture не работает и не переходит к последующим будущим.
Я попробовал следующее, основанное на ответе Питера Нейнса, но дал мне ошибку времени выполнения.
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
}
Ошибка выполнения я 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
Ответы
Ответ 1
Вы можете использовать функцию Future.recoverWith
, чтобы настроить исключение, если сбой Future
.
val failed = Future.failed(new Exception("boom"))
failed recoverWith {
case e: Exception => Future.failed(new Exception("A prettier error message", e)
}
Это приведет к слегка уродливому пониманию:
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
Обратите внимание, что вы также можете определить свои собственные исключения вместо Exception
, если вы хотите добавить больше информации, чем просто сообщение об ошибке.
Если вы не хотите, чтобы мелкозернистый элемент управления устанавливал другое сообщение об ошибке в зависимости от Throwable
в неудавшемся Future
(например, с callFuture1
), вы могли бы обогатить Future
, используя неявный класс для установки пользовательское сообщение об ошибке несколько проще:
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))
}
}
Что вы можете использовать, например:
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
В обоих случаях, используя errorMsg
или recoverWith
напрямую, вы все равно полагаетесь на Future
, поэтому, если Future
терпит неудачу, следующий Futures
не будет выполнен, и вы можете напрямую использовать сообщение об ошибке внутри сбой Future
.
Вы не указали, как вы хотите обрабатывать сообщения об ошибках. Если, например, вы хотите использовать сообщение об ошибке для создания другого Response
, вы можете использовать recoverWith
или recover
.
future3 recover { case e: Exception =>
val errorMsg = e.getMessage
InternalServerError(errorMsg)
}
Ответ 2
Скажите future1
, future2
и future3
throw Throwable
исключения с именами Future1Exception
, Future2Exception
и Future3Exception
соответственно. Затем вы можете вернуть соответствующую ошибку Response
из метода getResponse()
следующим образом:
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(...)
}
}
Согласно документации Future.recover
Создает новое будущее, которое будет обрабатывать любые подходящие кавычки, чтобы это будущее может содержать.