【问题标题】:Scala Future error handling with EitherScala Future 错误处理与 Either
【发布时间】:2015-12-28 05:30:10
【问题描述】:

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

这是一个包含两种情况的 sn-p,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")
    }
  }

这是在这两种情况下获得结果的 sn-p:

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] 会使事后获得响应变得更加冗长,如果我想将结果映射到另一个对象,它会变得更加复杂。这是要走的路,还是有更好的选择?

【问题讨论】:

标签: scala api exception error-handling


【解决方案1】:

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

然后考虑语言结构的这两个特征:arity(它会聚合一个或多个项目吗?)和 mode(同步时阻塞读取操作直到准备好或异步如果没有)。

所有这些都意味着Try 构造和块管理同步生成结果的块的成功或失败。您将控制您的资源是否提供正确的答案而不会遇到问题(由异常描述的问题)。

另一方面,Future 是一种异步的Try。这意味着它在没有发现任何问题(异常)时成功完成,然后通知其订阅者。因此,在这种情况下,我认为您不应该拥有 Either 的未来,也就是说,您的第二个 handleRequest 实现是使用期货的正确方式。

最后,如果你的困扰是抛出异常,你可以按照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
}

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

【讨论】:

  • 感谢您的承诺,我想我会使用它。
  • @nmat 你来得正好。我会说您提出的第二种方法更清洁。在我看来,从其他使用地图构建新的未来似乎是一种非常直接的方法。
【解决方案2】:

您可以为此使用特殊类型: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")
    }
  }
}

【讨论】:

    猜你喜欢
    • 2016-01-23
    • 2015-01-26
    • 1970-01-01
    • 2015-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-23
    相关资源
    最近更新 更多