以下Scala代码使用猫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“。
如何正确从异常中恢复?
答案 0 :(得分:1)
那是因为doException
正在内联引发异常。如果要使用Either
,则必须返回 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)
}
}
答案 1 :(得分:0)
我倾向于认为这似乎有些令人费解,但是为了进行练习,我相信有些事情并没有完全点击。
第一个事实是您抛出了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")))
}
不太正确的第二点是异常的恢复。当您在recoverWith
上调用EitherT
时,您正在定义从Left
的{{1}}到另一个EitherT
的部分函数。就您而言,应该是:
EitherT
如果您想要恢复失败的未来,我认为您需要对其进行明确恢复。像这样:
ServiceError => AsyncResult[Int]
如果您确实要使用AsyncResult {
result.value.recover {
case _: Throwable => {
println("recovered exception")
Right(99)
}
}
}
,则可以这样写:
recoverWith