【问题标题】:Scala - Futures and EithersScala - 期货和二选一
【发布时间】:2015-07-09 02:08:24
【问题描述】:

这个帖子让我知道了如何构建我的代码:Scala-way to handle conditions in for-comprehensions?

有问题的部分:

// First create the JSON
val resultFuture: Future[Either[Failure, JsResult]] = for {
  userRes <- userDao.findUser(userId)
  user    <- userRes.withFailure(UserNotFound).right
  authRes <- userDao.authenticate(user)
  auth    <- authRes.withFailure(NotAuthenticated).right
  goodRes <- goodDao.findGood(goodId)
  good    <- goodRes.withFailure(GoodNotFound).right
  checkedGood <- checkGood(user, good).right
} yield renderJson(Map("success" -> true)))

这是我不明白的行:

user    <- userRes.withFailure(UserNotFound).right
authRes <- userDao.authenticate(user)

userRes.withFailure(UserNotFound).right 映射到 userDao.authenticate(user)。这将创建一个新的 Either,其右侧有一个 Future,对吗?

怎么可能

val resultFuture: Future[Either[Failure, JsResult]]

属于它的类型。我认为应该有另一个未来,而不是 JsResult。 谁能给我解释一下?

编辑:既然 cmbaxter 和 Arne Claassen 确认了这一点,那么新的问题是:我应该如何编写这段代码,这样它看起来才不会难看,而且干净且结构化?

【问题讨论】:

  • 我同意这个答案有问题。正如已多次发布的那样,您不能在理解中混合类型。无论您从什么类型开始,都是您需要通过其余步骤传播的类型 (flatMaps)。在这种情况下,因为findUser 返回Future[Option[User]],所以Future 成为您必须在其余步骤中继续使用的类型。您不能像此人在理解的第二步中所做的那样只切换到Either
  • 我自己有点困惑。通常,for 理解一次只对一种 Monad 起作用,在这里我们混合了产生 Futures 和 Eithers 的函数。我无法解析原始示例

标签: scala dictionary future either


【解决方案1】:

我相信当Future 已经完全能够传达失败时,您收到的答案不必要地将Either 混入其中。您缺少的主要内容是一种从 Option 获取选项值而不显式抛出异常的方法。

我建议您将 Failures 对象更改为以下内容:

object Failures {

  sealed trait Failure extends Exception

  // Four types of possible failures here
  case object UserNotFound extends Failure

  case object NotAuthenticated extends Failure

  case object GoodNotFound extends Failure

  case object NoOwnership extends Failure

  // Put other errors here...

  // Converts options into Futures
  implicit class opt2future[A](opt: Option[A]) {
    def withFailure(f: Failure) = opt match {
      case None => Future.failed(f)
      case Some(x) => Future.successful(x)
    }
  }
}

现在您可以将 Future[Option[A]] 映射到 Future[A] 并指定失败条件,从而得到如下的 for 理解:

def checkGood(user: User, good: Good) =
  if (checkOwnership(user, good))
    Future.successful(good)
  else
    Future.failed(NoOwnership)

val resultFuture: Future[JsResult] = for {
  userOpt <- userDao.findUser(userId)
  user <- userOpt.withFailure(UserNotFound)
  authOpt <- userDao.authenticate(user)
  auth <- authOpt.withFailure(NotAuthenticated)
  goodOpt <- goodRes.withFailure(GoodNotFound)
  checkedGood <- checkGood(user, good)
} yield renderJson(Map("success" -> true))))

现在您有了Future[JsResult],您可以将失败的场景映射到您想要的输出,而成功的场景就是 JsResult。希望您在一个异步框架中使用它,该框架期望您为它提供一个未来,并且有它自己的失败未来到错误响应映射(例如 Play!)。

【讨论】:

  • 有没有办法做到这一点,而不必扩展“异常”?
  • 不干净了。 Future 的失败条件需要一个 Throwable。如果你不辜负未来,你就不能为了理解而短路未来。除了让你想到丑陋的Java try/catch 流之外,还有什么特别的理由要避开 Exception 吗?另外,异常是把请求失败映射到响应的Play方式,所以是惯用的方式。
  • 好的,非常感谢。只是将异常用于预期行为根本感觉不对:D
  • 我猜至少在这个例子中,这不是预期的行为,因为 for 理解假设每个 DAO 步骤都会产生一个值来输入下一个,所以得到一个 None 在对于这个工作流程来说,至少是意外行为
  • user
猜你喜欢
  • 2019-03-23
  • 1970-01-01
  • 2013-04-27
  • 2015-07-22
  • 2021-03-02
  • 1970-01-01
  • 2015-04-19
  • 2017-11-18
  • 2013-12-16
相关资源
最近更新 更多