无法处理未来失败的异常

unable handle exception from future failure

我希望以下代码在 callfuture1()callfuture2() 方法之一抛出异常时 return 自定义消息。我的理解是,如果任何一个未来失败,f 将是一个失败的未来。

但是,当callfuture1抛出异常。 f.onFailure 未执行。相反,我看到调用堆栈停在 callFuture1() 中发生异常的代码行处,并且标准内部错误是 returned。为什么会这样?

val f = for {
 x <- callfuture1()
 y <- callfuture2()
} yield y

f.onFailure {
 //send an internalserver error with some custom message
}

f.map {
 //send data back
}

====更新====

我从回复中看到,潜在的问题是 Exception 被抛出到 Future 之外,因此我的代码无法捕获那个失败的 future。 所以我更改了代码,使异常仅在未来发生。我仍然无法解释我所看到的行为。 (不知是否与Play框架有关。)

def controllerfunction(id: String) = Action.async{

  val f = for{
    x <- callfuture1(id)
    y <- callfuture2(x)
  } yield y

  y.onFailure{case t => 
    println("This gets printed");
    Ok("shit happened, but i am still ok")}

  y.map{resp:String => Ok(resp)}

}

def callfuture1(id: String):Future[Obj1] = {
  for {
    val1 <- callfuture1.1(id)
    val2 <- callfuture1.2(val1)
  } yield val2
}

def callfuture1.2:Future[Obj3] = Future{
  thrown new Exception("TEST ME");
}

def callfuture 1.1:Future[Obj4] = {...}
def callfuture2: Future[String] = {....}

期待。 方法 callfuture1.2 在未来抛出一个异常,所以我的期望是 onFailure 应该被执行,(它确实被执行),响应 returned 应该 "Shit happened, but i am still ok"

实际情况 播放框架 returns InternalServerError,我在控制台上看到错误堆栈。我看到 printlin("This gets printed") 正在执行。

无法理解发生了什么。有什么见解吗?

====更新2 =====

我确认该问题仅在调用 play 框架的内部控制器时发生(我使用的是 play 2.5)。作为一个独立的 Scala 程序,一切都按预期工作。我相信播放错误处理会捕获未处理的异常并打印堆栈跟踪。我认为这应该只发生在开发环境中。

似乎在 callfuture1() 中,您并没有像

那样将所有过程都包装在 Future 构造函数中
def callfuture1(): Future[?] = Future {
  val x = ...
  x
}

但您的代码似乎是

def callfuture1(): Future[?] = {
  val x = ... // some error happen here
  Future(x)
}

所以因为它不在未来,所以你的错误是直接扔进你的程序代码

如果 callfuture1 抛出 "outside of a future",就会发生这种情况。 你的理解力被脱糖成:

val f = callfuture1.flatMap{ x =>
  callfuture2.map{ y =>
    y
  }
}

如果 callfuture2 立即抛出(而不是 return 失败的未来),你仍然会以失败的未来结束,因为 callfuture2 在 [=15] 中被调用=],它捕获异常并将它们变成失败的期货(与 Future.map 相同)。

callfuture1的情况不同:如果它立即抛出,则没有封闭的Future.mapFuture.flatMap将其变成失败的未来。

一般来说,您应该尽量避免使用 return 是 Future 并且可能 抛出错误的方法。 这意味着如果 callfuture1 做任何可以抛出的事情,它应该捕获它并在你然后 return.

失败的未来转向异常

更新:关于您对 "Shit happened, but i am still ok" 的预期 return 的更新:

正如 Dima 在评论中已经暗示的那样,Future.onFailure 只能用于副作用。期货是不可变的。如果你想从一个失败的异常中恢复,没有办法修改原来的(失败的)未来,你所能做的就是将它转换成一个新的未来。 看看 Future.recover。它完全满足您的需求,即它允许通过匹配失败的结果(如果有)并将其转换为成功的未来来转换输入未来。它相当于 catch 条款,但适用于期货。具体来说,你真正想做的是这样的:

def controllerfunction(id: String) = Action.async{
  val f = for{
    x <- callfuture1(id)
    y <- callfuture2(x)
  } yield y

  f.map{ resp: String => 
    Ok(resp)
  }.recover{
    case t: Throwable => 
      println("This gets printed");
      Ok("shit happened, but i am still ok")
  }
}