简体   繁体   English

使用Either进行Scala Future错误处理

[英]Scala Future error handling with Either

I am writing a wrapper for an API and I want to do error handling for applications problems. 我正在编写API的包装器,我想对应用程序问题进行错误处理。 Each request returns a Future so in order to do this I see 2 options: using a Future[Either] or using exceptions to fail the future immediately. 每个请求都返回一个Future,所以为了做到这一点,我看到了两个选项:使用Future[Either]或使用exception来立即失败。

Here is a snippet with both situations, response is a future with the return of the HTTP request: 以下是两种情况的代码段, 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")
    }
  }

And here is the snippet to get the result in both cases: 以下是在两种情况下获得结果的片段:

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")
}

I don't like to use exceptions, but using Future[Either] makes it much more verbose to get the response afterwards, and if I want to map the result into another object it gets even more complicated. 我不喜欢使用异常,但是使用Future[Either]使之后获得响应变得更加冗长,如果我想将结果映射到另一个对象,它会变得更加复杂。 Is this the way to go, or are there better alternatives? 这是要走的路,还是有更好的选择?

Let me paraphrase Erik Meijer and consider the following table: 让我解释一下Erik Meijer并考虑下表:

在此输入图像描述

Consider then this two features of a language construct: arity (does it aggregate one or many items?) and mode (synchronous when blocking read operations until ready or asynchronous when not). 然后考虑一下语言结构的这两个特性: arity (它是聚合一个还是多个项?)和模式 (阻塞读操作时同步,直到准备好或异步时)。

All of this imply that Try constructs and blocks manage the success or failure of the block generating the result synchronously. 所有这些都意味着Try构造和块管理同步生成结果的块的成功或失败。 You'll control whether your resources provides the right answer without encountering problems (those described by exceptions). 您可以控制资源是否提供正确答案而不会遇到问题(例外情况描述的问题)。

On the other hand a Future is a kind of asynchronous Try . 另一方面, Future是一种异步Try That means that it successfully completes when no problems (exceptions) has been found then notifying its subscribers. 这意味着它在没有发现任何问题(例外)然后通知其订户时成功完成。 Hence, I don't think you should have a future of Either in this case , that is your second handleRequest implementation is the right way of using futures. 因此, 我不认为你应该有一个未来Either在这种情况下 ,这是你第二次handleRequest实现是利用期货的正确方法。

Finally, if what disturbs you is throwing an exception, you could follow the approach of Promises : 最后,如果扰乱你的是抛出异常,你可以遵循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
}

Or: 要么:

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
}

I'd rather use your second approach. 我宁愿使用你的第二种方法。

You could use special type for that: EitherT from the scalaz library . 您可以使用该特殊类型: EitherTscalaz库

It works with scalaz enhanced version of Either : \\/ 它适用于scalaz增强版的Either\\/

It could transform combination of any monad and \\/ into a single monad. 它可以将任何monad和\\/组合转换为单个monad。 So using scalaz instances for scala.concurent.Future you could achieve the desired mix. 因此,对于scala.concurent.Future使用scalaz实例,您可以实现所需的混合。 And you could go further with monad transformers if you wish. 如果你愿意,你可以进一步使用monad变形金刚。 Read this beautiful blog if you're interested. 如果您有兴趣,请阅读这个美丽的博客。

Here not prettified but working with scalaz 7.1 example for you: 这里没有美化,但为您使用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")
    }
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM