摆脱 Scala Future 嵌套
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/20276872/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Get rid of Scala Future nesting
提问by DanielKhan
Again and again I am struggling when a function relies on some future results. This usually boils down to a result like Future[Seq[Future[MyObject]]]
当一个函数依赖于一些未来的结果时,我一次又一次地挣扎。这通常归结为这样的结果 Future[Seq[Future[MyObject]]]
To get rid of that I now use Await inside a helper function to get a non-future object out and reduce the nesting.
为了摆脱这种情况,我现在在辅助函数中使用 Await 来获取非未来对象并减少嵌套。
It looks like this
看起来像这样
def findAll(page: Int, perPage: Int): Future[Seq[Idea]] = {
val ideas: Future[Seq[Idea]] = collection.find(Json.obj())
// [...]
ideas.map(_.map { // UGLY?
idea => {
// THIS RETURNED A Future[JsObject] before
val shortInfo: JsObject = UserDao.getShortInfo(idea.user_id)
idea.copy(user_data = Some(shortInfo))
}
})
}
This code works but to me it looks quite hacky. The two map calls are another flaw. I spent hours trying to figure out how to keep this completely asynchronous and returning a simple future Seq. How can this be solved using Play2 best practices?
这段代码有效,但对我来说它看起来很hacky。两个地图调用是另一个缺陷。我花了几个小时试图弄清楚如何保持这个完全异步并返回一个简单的未来 Seq。如何使用 Play2 最佳实践解决这个问题?
EditTo make the usecase more clear:
编辑为了使用例更清晰:
I have an object A from mongodb (reactivemongo) and want to add information coming from another call to mongodb getShortInfo. It's a classical "get user for this post" case that would be solved with a join in RDBMS.
getShortInfonaturally would produce a Future because of the call to the db.
To reduce the nesting within findAllI used Await(). Is this a good idea?
我有一个来自 mongodb (reactivemongo) 的对象 A 并且想要添加来自另一个对 mongodb 的调用的信息getShortInfo。这是一个经典的“为这篇文章获取用户”的案例,可以通过加入 RDBMS 来解决。
getShortInfo由于对数据库的调用,自然会产生一个 Future。为了减少嵌套,findAll我使用了 Await()。这是一个好主意吗?
findAllis called from an asynchronous Play action, converted into Json and sent over the wire.
findAll从异步 Play 操作调用,转换为 Json 并通过网络发送。
def getIdeas(page: Int, perPage: Int) = Action.async {
for {
count <- IdeaDao.count
ideas <- IdeaDao.findAll(page, perPage)
} yield {
Ok(Json.toJson(ideas))
}
}
So I think returning a Seq[Future[X]]from findAll won't bring better performance as I have to wait for the result anyways. Is this correct?
所以我认为Seq[Future[X]]从 findAll返回 a不会带来更好的性能,因为无论如何我都必须等待结果。这个对吗?
The usecase in short: Take a Future call returning a Sequence, use each element of the result to create another Future call, return the result to an asynchronous action in a way that no blocking situations should occur.
简而言之用例:以一个 Future 调用返回一个 Sequence,使用结果的每个元素创建另一个 Future 调用,将结果返回给一个异步操作,不会发生阻塞情况。
回答by stew
Two handy functions on the Future companion object you should know could help here, the first, and easier to wrap your head around is Future.sequence. It takes a sequnce of futures and returns a Future of a sequence. If are ending up with a Future[Seq[Future[MyObject]]], lets call that result. then you can change this to a Future[Future[Seq[MyObject]]]with result.map(Future.sequence(_))
您应该知道的 Future 伴生对象上的两个方便的函数在这里可以提供帮助,第一个更容易理解的是Future.sequence. 它需要一个期货序列并返回一个序列的期货。如果以 a 结尾Future[Seq[Future[MyObject]]],我们称之为result。然后你可以把它改成Future[Future[Seq[MyObject]]]withresult.map(Future.sequence(_))
Then to collapse a Future[Future[X]]for any X, you can run "result.flatMap(identity)", in fact, you can do this for any M[M[X]]to create a M[X]as long as Mhas flatMap.
然后要Future[Future[X]]为任何 X折叠 a ,您可以运行“result.flatMap(identity)”,事实上,您可以为任何M[M[X]]创建 aM[X]只要M有flatMap。
Another useful function here is Future.traverse. It is basically the result of taking a Seq[A], mapping it to a Seq[Future[B]], then running Future.sequence to get a Future[Seq[B]]So in your example, you'd have:
另一个有用的函数是Future.traverse. 它基本上是采取 a Seq[A],将其映射到 a Seq[Future[B]],然后运行 Future.sequence 以获取 a Future[Seq[B]]So 在您的示例中的结果,您将拥有:
ideas.map{ Future.traverse(_){ idea =>
/*something that returns a Future[JsObject]*/
} }.flatMap(identity)
However, many times when you are running flatMap(identity), you could be turning a map into a flatMap, and this is the case here:
但是,很多时候在运行 flatMap(identity) 时,您可能会将地图转换为 flatMap,这里就是这种情况:
ideas.flatMap{ Future.traverse(_) { idea =>
/*something that returns a Future[JsOjbect]*/
} }
回答by Mansoor Siddiqui
The Akka documentationhas a nice overview on how to deal with a compositions of futures. In general, it outlines four methods in scala.concurrent.Futurethat can be used to reduce a composition of futures into a single Future instance:
该阿卡文件对如何应对未来的一个组成很好的概述。一般来说,它概述了scala.concurrent.Future中的四种方法,可用于将期货的组合减少到单个 Future 实例中:
Future.sequenceFuture.traverseFuture.foldFuture.reduce
Future.sequenceFuture.traverseFuture.foldFuture.reduce

