以下Scala代码使用cats 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()
,它会抛出一个异常,但我希望它会打印"已恢复的异常"one_answers"99"。
如何正确地从异常中恢复?
这是因为doException
正在内联抛出异常。如果你想使用Either
,你必须返回Future(Left(exception))
,而不是抛出它。
我觉得你想得太多了。这里似乎不需要Either
。。。或CCD_ 10。
为什么不做一些简单的事情,比如:
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)
}
}
我倾向于同意这似乎有点复杂,但为了练习,我相信有几件事不太符合要求。
第一个是抛出Exception,而不是将其捕获为Future语义的一部分。例如,您应该将方法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")))
}
第二个不太正确的部分是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))
}
}