繁体   English   中英

如何减少链期货的嵌套

[英]How to reduce nesting on chain futures

大多数时候,我的 Future[T] 操作依赖于链中以前的未来。 我大部分时间都在使用带有模式匹配的 flatMap 函数。 如;

findUser(userId).flatMap {
  case None => Future.successful(NotFound("No user with given id"))
  case Some(user) => findAddress(user.addressId).flatMap {
    case None => Future.successful(NotFound("No address with given id"))
    case Some(address) => findCity(address.cityId).flatMap {
      case None => Future.successful(NotFound("No city with given id"))
      case Some => Future.successful(Ok)
    }
  }
}

通过这种方式,我能够返回与问题相关的对象,所有分支都在处理中。 但在我看来(以及我的代码阅读乐趣)这种方法的缺点是嵌套了很多。 此外,如果行太长,即使格式正确,也无法跟踪哪个 case 语句是哪个。 所以它转到编辑器的右下角。

另一种方式建议可能用于理解。 下面是上面代码的等价物。 但不同之处在于,如果不满足 if-guard,则 for-comp 会引发异常。 它还返回一个选项,以便在我想使用的任何地方调用 get 方法(我不想这样做);

val items = for {
  user <- findUser(userId) if user.isDefined
  address <- findAddress(user.addressId) if address.isDefined
  city <- findCity(address.cityId) if address.isDefined
} yield (user.get, address.get, city.get)

再次有人可能会建议捕获异常,但正如我从许多来源中看到的那样,捕获异常被认为是不好的。 此外,异常不会提供哪个 case 语句不满足条件。

同样的事情也适用于 return 语句。 由于我自己来自基于 java 和 .net 的语言,我倾向于使用下面的样式。

val user = Await.result(findUser(userId), timeout)
if (user.isEmpty) {
  return Future.successful(NotFound("No user with given id"))
}

val address = Await.result(findAddress(user.get.addressId), timeout)
if (address.isEmpty) {
  return Future.successful(NotFound("No address with given id"))
}

val city = Await.result(findUser(address.get.cityId), timeout)
if(city.isEmpty) {
  return Future.successful(NotFound("No city with given id"))
}

Future.successful(Ok)

在我的理解中,这绝对是毫无疑问的。 首先,它使代码块阻塞,其次,它再次迫使我使用获取值并使用返回块,这与在缩短执行时间方面的抛出异常类似。

还没有找到一个优雅的解决方案。 我目前正在使用嵌套方法,这使得它更难阅读

谢谢

您应该使用.failed期货而不是successful来传达异常情况:

sealed trait NotFoundErr
class NoUser extends Exception("No user with given id") with NotFoundErr
class NoAddress extends Exception("No address with given id") with NotFoundErr
class NoCity extends Exception("No city with given id") with NotFoundErr

def getOrElse[T](ifNot: Exception)(what: => Future[Option[T]]) = what
  .map(_.getOrElse(throw ifNot))

val items = for {
  user <- getOrElse(new NoUser)(findUser(userId))
  address <- getOrElse(new NoAddress)(findAddress(user.addressId))
  city <- getOrElse(new NoCity)(findCity(address.cityId))     
} yield (user, address, city)

items
 .map(_ => Ok)
 .recover { case e: Exception with NotFoundErr => NotFound(e.getMessage) }

你可以用一个隐式让它看起来更漂亮:

object RichFuture {
   implicit class Pimped[T](val f: Future[Option[T]]) extends AnyVal {
      def orElse(what: => T) = f.map(_.getOrElse(what))
   }
}

现在,你可以写出这样的理解:

for {
    user <- findUser(userId) orElse throw(new NoUser)
    address <- findAddress(user.addressId) orElse throw(new NoAddress)
    city <- findCity(address.cityId) orElse throw(new NoCity)
} yield (user, address, city)

这个问题的优雅解决方案是使用适当的数据类型来包装不同的失败案例。

我建议你调查一下

Cats Validation 或Scalaz Validation

这些类型收集操作结果并在理解和可能的未来中很好地组合

暂无
暂无

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

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