无法在Scala的Future中恢复异常

时间:2018-11-13 14:42:09

标签: scala scala-cats

以下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“。

如何正确从异常中恢复?

2 个答案:

答案 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