web-dev-qa-db-ja.com

エラー処理Scala:理解のための未来

私のプレイでエラー処理を行いたいscala Webアプリケーション。

私のアプリケーションはデータベースと通信して、いくつかの行をフェッチします。次のフローに従います。

  1. 最初にdbを呼び出してデータをフェッチします
  2. 最初の呼び出しでデータを使用して、dbから他のデータをフェッチします
  3. 最後の2つの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は、callFuture1の後でのみ実行されるためです。 callFuture1が失敗またはエラーが発生し、すぐにエラーメッセージを返したい場合、callFuture2を実行したくありません。 (callFuture2とcallFuture3も同じ)

-編集-

CallFutureのいずれかが失敗し、後続のfutureCallsに進まない場合、getResponse()メソッドから適切なエラー応答を返そうとしています。

Peter Neyensの回答に基づいて以下を試しましたが、実行時エラーが発生しました。

 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
  }

実行時エラー

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

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の代わりに使用する独自の例外を定義することもできます。

失敗したThrowableFutureに応じて、細かい制御で別のエラーメッセージを設定したくない場合(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)
}
27
Peter Neyens

future1future2future3がそれぞれFuture1ExceptionFuture2ExceptionFuture3Exceptionという名前のThrowable例外をスローするとします。その後、次のようにgetResponse()メソッドから適切なエラーResponseを返すことができます。

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

このフューチャーに含まれる可能性のある一致するスロー可能オブジェクトを処理する新しいフューチャーを作成します。

3
Mario Galic