简体   繁体   English

如何减少链期货的嵌套

[英]How to reduce nesting on chain futures

Most of the time my Future[T] operations are dependent to previous future in the chain.大多数时候,我的 Future[T] 操作依赖于链中以前的未来。 I am using flatMap function with pattern matching most of the time.我大部分时间都在使用带有模式匹配的 flatMap 函数。 Such as;如;

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

with this way i am able to return an object related with the problem, all branchings are being handled.通过这种方式,我能够返回与问题相关的对象,所有分支都在处理中。 But downside of this approach in my opinion (and my code reading pleasure) it is getting nested a lot.但在我看来(以及我的代码阅读乐趣)这种方法的缺点是嵌套了很多。 Also if the lines are too long it is impossible to track which case statement is which even with a proper formatting.此外,如果行太长,即使格式正确,也无法跟踪哪个 case 语句是哪个。 So that goes to the right-bottom side of the editor.所以它转到编辑器的右下角。

The other way one would suggest might using for comprehension.另一种方式建议可能用于理解。 Below is kind of a equivalent of the code above.下面是上面代码的等价物。 But the difference is for-comp one is throwing an exception if the if-guard is not satisfied.但不同之处在于,如果不满足 if-guard,则 for-comp 会引发异常。 Also it returns an option to use which wherever i want to use i need to call get method (which i don't want to do);它还返回一个选项,以便在我想使用的任何地方调用 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)

Again one may suggest catching the exception but as i read from many sources catching exceptions are considered not good.再次有人可能会建议捕获异常,但正如我从许多来源中看到的那样,捕获异常被认为是不好的。 Also the exception wouldn't provide which case statement didn't satisfy the condition.此外,异常不会提供哪个 case 语句不满足条件。

Same thing applies for return statements as well.同样的事情也适用于 return 语句。 As myself coming from java and .net based languages, i am inclined to use the style below.由于我自己来自基于 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)

which is definitely out of question in my understanding.在我的理解中,这绝对是毫无疑问的。 First of all it makes the code-block blocking, secondly again it forces me to use get values and uses return blocks which are similar with the throwing exceptions in the matter of cutting the execution short.首先,它使代码块阻塞,其次,它再次迫使我使用获取值并使用返回块,这与在缩短执行时间方面的抛出异常类似。

Haven't been able to find an elegant solution to this.还没有找到一个优雅的解决方案。 I am currently going with the nested approach which makes it harder to read我目前正在使用嵌套方法,这使得它更难阅读

Thanks谢谢

You should use .failed futures rather than successful to communicate exceptional conditions:您应该使用.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) }

You can make it look even fancier with an implicit:你可以用一个隐式让它看起来更漂亮:

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

Now, you can write the for-comprehension like:现在,你可以写出这样的理解:

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)

The elegant solution to this problem is to use an appropriate data type to wrap the different failure cases.这个问题的优雅解决方案是使用适当的数据类型来包装不同的失败案例。

I'd suggest you look into我建议你调查一下

Cats Validated or Scalaz Validation Cats Validation 或Scalaz Validation

Those types collects the operation outcome and compose well in comprehensions and possibly with futures这些类型收集操作结果并在理解和可能的未来中很好地组合

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

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