无法在 Scala 的 Future 中恢复异常
Unable to recover exception in Future in Scala
以下 Scala 代码使用 cat EitherT
将结果包装在 Future[Either[ServiceError, T]]
中:
package com.example
import com.example.AsyncResult.AsyncResult
import cats.implicits._
import scala.concurrent.ExecutionContext.Implicits.global
class ExternalService {
def doAction(): AsyncResult[Int] = {
AsyncResult.success(2)
}
def doException(): AsyncResult[Int] = {
println("do exception")
throw new NullPointerException("run time exception")
}
}
class ExceptionExample {
private val service = new ExternalService()
def callService(): AsyncResult[Int] = {
println("start callService")
val result = for {
num <- service.doException()
} yield num
result.recoverWith {
case ex: Throwable =>
println("recovered exception")
AsyncResult.success(99)
}
}
}
object ExceptionExample extends App {
private val me = new ExceptionExample()
private val result = me.callService()
result.value.map {
case Right(value) => println(value)
case Left(error) => println(error)
}
}
AsyncResult.scala 包含:
package com.example
import cats.data.EitherT
import cats.implicits._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
object AsyncResult {
type AsyncResult[T] = EitherT[Future, ServiceError, T]
def apply[T](fe: => Future[Either[ServiceError, T]]): AsyncResult[T] = EitherT(fe)
def apply[T](either: Either[ServiceError, T]): AsyncResult[T] = EitherT.fromEither[Future](either)
def success[T](res: => T): AsyncResult[T] = EitherT.rightT[Future, ServiceError](res)
def error[T](error: ServiceError): AsyncResult[T] = EitherT.leftT[Future, T](error)
def futureSuccess[T](fres: => Future[T]): AsyncResult[T] = AsyncResult.apply(fres.map(res => Right(res)))
def expectTrue(cond: => Boolean, err: => ServiceError): AsyncResult[Boolean] = EitherT.cond[Future](cond, true, err)
def expectFalse(cond: => Boolean, err: => ServiceError): AsyncResult[Boolean] = EitherT.cond[Future](cond, false, err)
}
ServiceError.scala 包含:
package com.example
sealed trait ServiceError {
val detail: String
}
在ExceptionExample
中,如果它调用service.doAction()
,它会按预期打印2,但如果它调用service.doException()
,它会抛出异常,但我预计它会打印"recovered exception" 和 "99".
如何正确地从异常中恢复?
那是因为 doException
正在抛出内联异常。如果你想使用Either
,你必须return Future(Left(exception))
而不是扔掉它。
我觉得,你有点想多了。看起来您在这里不需要 Either
... 或 cats
。
为什么不做一些简单的事情,像这样:
class ExternalService {
def doAction(): Future[Int] = Future.successful(2)
def doException(): AsyncResult[Int] = {
println("do exception")
Future.failed(NullPointerException("run time exception"))
// alternatively: Future { throw new NullPointerExceptioN() }
}
class ExceptionExample {
private val service = new ExternalService()
def callService(): AsyncResult[Int] = {
println("start callService")
val result = for {
num <- service.doException()
} yield num
// Note: the aboive is equivalent to just
// val result = service.doException
// You can write it as a chain without even needing a variable:
// service.doException.recover { ... }
result.recover { case ex: Throwable =>
println("recovered exception")
Future.successful(99)
}
}
我倾向于认为它看起来有点令人费解,但为了练习,我相信有几件事不太清楚。
第一个是您抛出异常而不是将其捕获为 Future 语义的一部分。 IE。您应该将方法 doException
更改为:
def doException(): AsyncResult[Int] = {
println("do exception")
throw new NullPointerException("run time exception")
}
收件人:
def doException(): AsyncResult[Int] = {
println("do exception")
AsyncResult(Future.failed(new NullPointerException("run time exception")))
}
第二个不太正确的地方是异常的恢复。当您在 EitherT
上调用 recoverWith
时,您正在定义从 EitherT
的 Left
到另一个 EitherT
的偏函数。在你的情况下,那将是:
ServiceError => AsyncResult[Int]
如果你想要恢复失败的未来,我认为你需要明确地恢复它。类似于:
AsyncResult {
result.value.recover {
case _: Throwable => {
println("recovered exception")
Right(99)
}
}
}
如果你真的想使用recoverWith
,那么你可以这样写:
AsyncResult {
result.value.recoverWith {
case _: Throwable =>
println("recovered exception")
Future.successful(Right(99))
}
}
以下 Scala 代码使用 cat EitherT
将结果包装在 Future[Either[ServiceError, T]]
中:
package com.example
import com.example.AsyncResult.AsyncResult
import cats.implicits._
import scala.concurrent.ExecutionContext.Implicits.global
class ExternalService {
def doAction(): AsyncResult[Int] = {
AsyncResult.success(2)
}
def doException(): AsyncResult[Int] = {
println("do exception")
throw new NullPointerException("run time exception")
}
}
class ExceptionExample {
private val service = new ExternalService()
def callService(): AsyncResult[Int] = {
println("start callService")
val result = for {
num <- service.doException()
} yield num
result.recoverWith {
case ex: Throwable =>
println("recovered exception")
AsyncResult.success(99)
}
}
}
object ExceptionExample extends App {
private val me = new ExceptionExample()
private val result = me.callService()
result.value.map {
case Right(value) => println(value)
case Left(error) => println(error)
}
}
AsyncResult.scala 包含:
package com.example
import cats.data.EitherT
import cats.implicits._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
object AsyncResult {
type AsyncResult[T] = EitherT[Future, ServiceError, T]
def apply[T](fe: => Future[Either[ServiceError, T]]): AsyncResult[T] = EitherT(fe)
def apply[T](either: Either[ServiceError, T]): AsyncResult[T] = EitherT.fromEither[Future](either)
def success[T](res: => T): AsyncResult[T] = EitherT.rightT[Future, ServiceError](res)
def error[T](error: ServiceError): AsyncResult[T] = EitherT.leftT[Future, T](error)
def futureSuccess[T](fres: => Future[T]): AsyncResult[T] = AsyncResult.apply(fres.map(res => Right(res)))
def expectTrue(cond: => Boolean, err: => ServiceError): AsyncResult[Boolean] = EitherT.cond[Future](cond, true, err)
def expectFalse(cond: => Boolean, err: => ServiceError): AsyncResult[Boolean] = EitherT.cond[Future](cond, false, err)
}
ServiceError.scala 包含:
package com.example
sealed trait ServiceError {
val detail: String
}
在ExceptionExample
中,如果它调用service.doAction()
,它会按预期打印2,但如果它调用service.doException()
,它会抛出异常,但我预计它会打印"recovered exception" 和 "99".
如何正确地从异常中恢复?
那是因为 doException
正在抛出内联异常。如果你想使用Either
,你必须return Future(Left(exception))
而不是扔掉它。
我觉得,你有点想多了。看起来您在这里不需要 Either
... 或 cats
。
为什么不做一些简单的事情,像这样:
class ExternalService {
def doAction(): Future[Int] = Future.successful(2)
def doException(): AsyncResult[Int] = {
println("do exception")
Future.failed(NullPointerException("run time exception"))
// alternatively: Future { throw new NullPointerExceptioN() }
}
class ExceptionExample {
private val service = new ExternalService()
def callService(): AsyncResult[Int] = {
println("start callService")
val result = for {
num <- service.doException()
} yield num
// Note: the aboive is equivalent to just
// val result = service.doException
// You can write it as a chain without even needing a variable:
// service.doException.recover { ... }
result.recover { case ex: Throwable =>
println("recovered exception")
Future.successful(99)
}
}
我倾向于认为它看起来有点令人费解,但为了练习,我相信有几件事不太清楚。
第一个是您抛出异常而不是将其捕获为 Future 语义的一部分。 IE。您应该将方法 doException
更改为:
def doException(): AsyncResult[Int] = {
println("do exception")
throw new NullPointerException("run time exception")
}
收件人:
def doException(): AsyncResult[Int] = {
println("do exception")
AsyncResult(Future.failed(new NullPointerException("run time exception")))
}
第二个不太正确的地方是异常的恢复。当您在 EitherT
上调用 recoverWith
时,您正在定义从 EitherT
的 Left
到另一个 EitherT
的偏函数。在你的情况下,那将是:
ServiceError => AsyncResult[Int]
如果你想要恢复失败的未来,我认为你需要明确地恢复它。类似于:
AsyncResult {
result.value.recover {
case _: Throwable => {
println("recovered exception")
Right(99)
}
}
}
如果你真的想使用recoverWith
,那么你可以这样写:
AsyncResult {
result.value.recoverWith {
case _: Throwable =>
println("recovered exception")
Future.successful(Right(99))
}
}