web-dev-qa-db-ja.com

Futureのリカバリーが例外をキャッチしないのはなぜですか?

私はScala、Play Framework 2.1.x、reactivemongoドライバーを使用しています。

私はAPI呼び出しがあります:

def getStuff(userId: String) = Action(implicit request => {
    Async {
      UserDao().getStuffOf(userId = userId).toList() map {
        stuffLst => Ok(stuffLst)
      } 
    }
})

99%の確率で正常に動作しますが、失敗する場合があります(理由に関係なく、それは問題ではありません)。

エラーが発生した場合に回復したかったので、追加しました:

recover { case _ => BadRequest("")}

しかし、これはエラーから私を回復しません。
scalaコンソールで同じコンセプトを試しましたが、うまくいきました:

import scala.concurrent._
import scala.concurrent.duration._
import ExecutionContext.Implicits.global
var f = future { throw new Exception("") } map {_ => 2} recover { case _ => 1}
Await.result(f, 1 nanos)

これは期待どおり1を返します。
私は現在、非同期を次のようにラップしています:

try{
  Async {...}
} catch {
  case _ => BadRequest("")
} 

そして、これはエラーをキャッチします。

私はネットでScalaのFutureのドキュメントをいくつか調べましたが、なぜリカバリが機能しないのか困惑しています。

なぜ誰か知っていますか?それを整理するために私は何を見逃していますか?

21
samz

失敗する理由は、実際には100%重要です。コードを数行のコードに分割すると、その理由が理解できます。

_def getStuff(userId: String) = Action(implicit request => {
  Async {
    val future = UserDao().getStuffOf(userId = userId).toList()
    val mappedFuture = future.map {
      stuffLst => Ok(stuffLst)
    }
    mappedFuture.recover { case _ => BadRequest("")}
  }
})
_

したがって、UserDao().getStuffOf(userId = userId).toList()は未来を返します。未来はまだ起こっていないかもしれない何かを表しています。その例外がスローされた場合は、リカバリーでその例外を処理できます。ただし、あなたのケースでは、エラーが発生していますフューチャーが作成される前でもUserDao().getStuffOf(userId = userId).toList()コールはフューチャーをスローしており、フューチャーを返していません。したがって、未来を回復するための呼び出しは実行されません。これは、Scala repl:

_import scala.concurrent._
import scala.concurrent.duration._
import ExecutionContext.Implicits.global
var f = { throw new Exception(""); future { "foo" } map {_ => 2} recover { case _ => 1} }
Await.result(f, 1 nanos) }
_

未来を作成するコードが発生する前に例外がスローされたので、そもそも未来を作成したことがないため、これは明らかに機能しません。

したがって、解決策は、UserDao().getStuffOf(userId = userId).toList()への呼び出しをtry catchブロックでラップするか、呼び出すメソッドで失敗している理由を見つけ、そこで例外をキャッチし、失敗したfutureを返すことです。

45
James Roper

Playの新しいバージョン(2.2.xなど)を使用している場合は、次のようにできます。

def urlTest() = Action.async {
    val holder: WSRequestHolder = WS.url("www.idontexist.io")
    holder.get.map {
      response =>
        println("Yay, I worked")
        Ok
    }.recover {
      case _ =>
        Log.error("Oops, not gonna happen")
        InternalServerError("Failure")
    }
}
4
catrapture