如何减少链条期货上的嵌套

时间:2017-06-23 12:01:51

标签: scala rest playframework

大多数时候,我的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)

同样可以建议捕获异常,但是当我从许多来源中读取异常被认为是不好的。此例外也不能提供哪种案例陈述不满足条件。

同样适用于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)

这在我的理解中肯定是不可能的。首先它使代码块阻塞,其次它又迫使我使用get值并使用返回块,这类似于在执行简短的事务中抛出异常。

Haven能够找到一个优雅的解决方案。我目前正在使用嵌套方法,这使得阅读更加困难

由于

2 个答案:

答案 0 :(得分:1)

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

我建议你研究一下

Cats ValidatedScalaz Validation

这些类型收集操作结果并在理解中很好地构成,并且可能与期货合作

答案 1 :(得分:0)

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

sealed  NotFoundErr
class NoUser extends Exception("No user with given id") NotFoundErr
class NoAddress extends Exception("No address with given id") NotFoundErr
class NoCity extends Exception("No city with given id") 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-comprehension,如:

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)