使用Either进行Scala Future错误处理

时间:2015-09-30 12:20:07

标签: scala api exception error-handling

我正在编写API的包装器,我想对应用程序问题进行错误处理。每个请求都返回一个Future,所以为了做到这一点,我看到了两个选项:使用Future[Either]或使用例外来立即失败。

以下是两种情况的代码段,response是返回HTTP请求的未来:

  def handleRequestEither: Future[Either[String, String]] = {
    response.map {
      case "good_string" => Right("Success")
      case _ => Left("Failed")
    }
  }

  def handleRequest: Future[String] = {
    response.map {
      case "good_string" => "Success"
      case _ => throw new Exception("Failed")
    }
  }

以下是在两种情况下获得结果的片段:

handleRequestEither.onComplete {
  case Success(res) =>
    res match {
      case Right(rightRes) => println(s"Success $res")
      case Left(leftRes) => println(s"Failure $res")
    }
  case Failure(ex) =>
    println(s"Failure $ex")
}

handleRequest.onComplete {
  case Success(res) => println(s"Success $res")
  case Failure(ex) => println(s"Failure $ex")
}

我不想使用异常,但是使用Future[Either]会使得后续获得响应更加冗长,如果我想将结果映射到另一个对象,则会变得更加复杂。这是要走的路,还是有更好的选择?

2 个答案:

答案 0 :(得分:4)

让我解释Erik Meijer并考虑下表:

enter image description here

然后考虑一下语言构造的这两个特性: arity (它是聚合一个还是多个项?)和模式(阻塞读取操作时同步,直到准备好或异步当没有)。

所有这些都暗示Try构造和块管理同步生成结果的块的成功或失败。您可以控制您的资源是否提供正确的答案而不会遇到问题(例外情况描述的问题)。

另一方面,Future是一种异步Try。这意味着它在没有发现任何问题(例外)然后通知其订户时成功完成。因此,我认为你不应该在这种情况下拥有Either ,这是你的第二个handleRequest实施是使用期货的正确方法。 / p>

最后,如果扰乱你的是抛出异常,你可以按照Promises的方法:

def handleRequest: Future[String] = {
   val p = Promise[String]
   response.map {
      case "good_string" => p.success("Success")
      case _ => p.failure(new Exception("Failed"))
    }
   p.future
}

或者:

case class Reason(msg: String) extends Exception

def handleRequest: Future[String] = {
   val p = Promise[String]
   response.map {
      case "good_string" => p.success("Success")
      case _ => p.failure(Reason("Invalid response"))
    }
   p.future
}

我宁愿使用你的第二种方法。

答案 1 :(得分:0)

您可以使用特殊类型:EitherT中的scalaz library

适用于Either\/

的scalaz增强版

它可以将任何monad和\/的组合转换为单个monad。因此,使用scala.concurent.Future的scalaz实例可以实现所需的混合。如果你愿意,你可以进一步使用monad变形金刚。 Read this beautiful blog if you're interested.

这里没有经过美化,但是为你准备了scalaz 7.1示例:

import scala.concurrent.duration.Duration
import scala.concurrent.{Await, Future}
import scalaz._
import scalaz.std.scalaFuture._
import EitherT._
import scala.concurrent.ExecutionContext.Implicits.global

object EitherFuture {
  type ETFS[X] = EitherT[Future, String, X]

  val IntResponse = "result (\\d+)".r

  def parse(response: Future[String]) =
    eitherT(response map {
      case IntResponse(num) ⇒ \/-(num.toInt)
      case _ ⇒ -\/("bad response")
    })

  def divideBy2(x: Validation[String, Int]) = 
    x.ensure("non divisible by 2")(_ % 2 == 0).map(_ / 2)

  def handleResponse(response: Future[String]) = for {
    num ← parse(response).validationed(divideBy2)
  } yield s"half is $num"

  def main(args: Array[String]) {
    Map(
      'good → "result 10",
      'proper → "result 11",
      'bad → "bad_string"
    ) foreach { case (key, str) ⇒
      val response = Future(str)
      val handled = handleResponse(response)
      val result = Await.result(handled.run, Duration.Inf)

      println(s"for $key response we have $result")
    }
  }
}