scala 为什么 Future 的恢复不捕获异常?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19431825/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 05:45:53  来源:igfitidea点击:

Why does Future's recover not catch exceptions?

scalaplayframeworkreactivemongo

提问by samz

I'm using Scala, Play Framework 2.1.x, and reactivemongo driver.

我正在使用 Scala、Play Framework 2.1.x 和reactivemongo 驱动程序。

I have an api call :

我有一个 api 调用:

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

It works fine 99% of the time but it may fail sometimes (doesn't matter why, that's not the issue).

它在 99% 的情况下都可以正常工作,但有时可能会失败(不管为什么,这不是问题)。

I wanted to recover in a case of an error so i added:

我想在出现错误的情况下恢复,所以我补充说:

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

But this does not recover me from errors.
I tried the same concept on the scala console and it worked:

但这并不能使我从错误中恢复过来。
我在 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)

This returns 1 as expected.
I currently wrapped the Async with:

这将按预期返回 1。
我目前包装了异步:

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

And this catches the errors.

这会捕获错误。

I went over some Scala's Future docs on the net and I'm baffled why recover did not work for me.

我在网上浏览了一些 Scala 的未来文档,我很困惑为什么恢复对我不起作用。

Does anyone know why? What do I miss to sort it out?

有谁知道为什么?我想解决什么问题?

回答by James Roper

Why it fails actually matters 100%. If we spread the code over a number of lines of code, you'll understand why:

为什么它失败实际上 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("")}
  }
})

So, UserDao().getStuffOf(userId = userId).toList()returns you a future. A future represents something that may not have happened yet. If that thing throws an exception, you can handle that exception in recover. However, in your case, the error is happening before the future is even being created, the UserDao().getStuffOf(userId = userId).toList()call is throwing an exception, not returning a future. So the call to recover the future will never be executed. It's equivalent to doing this in the Scala repl:

所以,UserDao().getStuffOf(userId = userId).toList()还你一个未来。未来代表可能尚未发生的事情。如果那个东西抛出异常,你可以在recover中处理那个异常。但是,在您的情况下,错误发生在甚至创建未来之前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) }

Obviously that doesn't work, since you never created the future in the first place beacuse the exception was thrown before the code to create the future happened.

显然这是行不通的,因为您从来没有创建过未来,因为在创建未来的代码发生之前抛出了异常。

So the solution is to either wrap your call to UserDao().getStuffOf(userId = userId).toList()in a try catch block, or find out why it's failing in whatever method you're calling, and catch the exception there, and return a failed future.

所以解决方案是要么将您的调用包装UserDao().getStuffOf(userId = userId).toList()在 try catch 块中,要么找出它在您调用的任何方法中失败的原因,并在那里捕获异常,并返回一个失败的未来。

回答by catrapture

If you have a later version of Play eg 2.2.x, you can do this:

如果你有更高版本的 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")
    }
}