为什么它不捕获并打印来自calculateTwo的异常消息?如果我们让calculateOne抛出异常,异常就会被捕获并打印消息。
package com.oxo.test
import cats.data.EitherT
import cats.implicits._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
object FutureError extends App {
try {
(for {
result <- EitherT(calculateOne())
resultTwo <- EitherT(calculateTwo())
resultThree <- EitherT(calculateThree())
sum = result + resultTwo + resultThree
} yield {
sum
})
} catch {
case e: Exception => println(e.getMessage)
}
private def calculateOne(): Future[Either[String, Int]] = {
//throw new RuntimeException("error from one")
Future.successful(Right(1))
}
private def calculateTwo(): Future[Either[String, Int]] = {
throw new RuntimeException("error from two")
//Future.successful(Right(2))
}
private def calculateThree(): Future[Either[String, Int]] = {
//throw new RuntimeException("error from three")
Future.successful(Right(3))
}
}
EitherT
和try ... catch
是两种完全正交的异常处理机制。为了处理像 EitherT
这样的结果类型类型的异常,我们只需调用类上的方法,而不是使用特殊的控制构造。例如,根据您的情况,getOrElse
是一个不错的选择。
(for {
result <- EitherT(calculateOne())
resultTwo <- EitherT(calculateTwo())
resultThree <- EitherT(calculateThree())
sum = result + resultTwo + resultThree
} yield {
sum
}).getOrElse(whateverDefaultValue)
请记住,处理异常并不意味着您可以返回适当类型的值。如果
sum
是 Int
,则处理错误条件 also 的结果也需要是 Int
。如果在这种情况下你没有返回 Int
,那么你就没有处理异常;你正在传播它。
正如其他人所说:将
throw/try..catch
与 Future/Either/EitherT
混合是自找麻烦。坚持使用一种或另一种型号。
现在,让我们了解代码中发生了什么。
您的示例可以简化为以下代码,其行为方式相同:
calculateOne()
.flatMap(_ => calculateTwo())
calculateTwo()
将在 Future
中“执行”,任何异常都会被 Future
吞噬并产生 Future.failed
。它与您所写的大致相同:
calculateOne.flatMap { _ =>
Future {
throw new Exception(...)
}
}
其本身大致相同于:
calculateOne.flatMap { _ =>
Future.failed(new Exception(...)) // no throw!
}
整体表达结果无一例外,只是一个
Future.failed
。
现在,如果
calculateOne()
抛出异常,它还没有“进入” Future
并且大致与: 相同
throw new Exception(...)
calculateTwo() // not even called, dead code
这只是抛出异常。
它不一样:
Future.failed(new Exception())
.flatMap(_ => calculateTwo())
这不会导致任何异常,但会失败
Future
。