Обработка ошибок 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

Создает новое будущее, которое будет обрабатывать любые подходящие кавычки, чтобы это  будущее может содержать.