无法在Scala的Future中恢复异常



以下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时,您正在定义一个从EitherTLeft到另一个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))
    }
  }

最新更新