无法处理未来失败的异常
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.map
或Future.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")
}
}
我希望以下代码在 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()
中,您并没有像
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.map
或Future.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")
}
}