私のプレイでエラー処理を行いたいscala Webアプリケーション。
私のアプリケーションはデータベースと通信して、いくつかの行をフェッチします。次のフローに従います。
以下は私の疑似コードです。
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] {...}
次のような状況で、エラー/失敗の処理をどのように実行しますか
-編集-
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
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)
}
future1
、future2
、future3
がそれぞれFuture1Exception
、Future2Exception
、Future3Exception
という名前の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
このフューチャーに含まれる可能性のある一致するスロー可能オブジェクトを処理する新しいフューチャーを作成します。