当一个函数依赖于未来的某些结果时,我一次又一次地挣扎。 这通常归结为像Future [Seq [Future [MyObject]]]
这样的结果为了摆脱这种情况,我现在在辅助函数中使用Await来获取非未来的对象并减少嵌套。
看起来像这样
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))
}
})
}
此代码有效,但对我来说它看起来很hacky。这两个地图调用是另一个缺陷。 我花了好几个小时试图弄清楚如何保持这种完全异步并返回一个简单的未来Seq。如何使用Play2最佳实践解决这个问题?
修改 为了使用例更清晰:
我有来自mongodb(reactivemongo)的对象A,并希望将来自另一个调用的信息添加到mongodb getShortInfo
。这是一个经典的“获得此帖子的用户”案例,可以通过RDBMS中的连接来解决。
getShortInfo
因为调用db而自然会产生Future。
为了减少findAll
内的嵌套,我使用了Await()。这是个好主意吗?
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))
}
}
所以我认为从findAll返回Seq[Future[X]]
不会带来更好的性能,因为我必须等待结果。这是对的吗?
简称用例: 使用Future调用返回一个Sequence,使用结果的每个元素创建另一个Future调用,将结果返回到异步操作,以便不会出现阻塞情况。
答案 0 :(得分:48)
你应该知道的Future伴侣对象上的两个方便的功能可以在这里帮助,第一个,更容易包围你的是Future.sequence
。它需要一个未来的序列并返回一个序列的未来。如果最终得到Future[Seq[Future[MyObject]]]
,请拨打result
。然后,您可以使用Future[Future[Seq[MyObject]]]
result.map(Future.sequence(_))
然后要为任何X折叠Future[Future[X]]
,您可以运行“result.flatMap(identity)”,事实上,您可以为任何M[M[X]]
执行此操作以创建M[X]
只要M
有flatMap
。
这里另一个有用的功能是Future.traverse
。这基本上是获取Seq[A]
,将其映射到Seq[Future[B]]
,然后运行Future.sequence以获得Future[Seq[B]]
的结果所以在您的示例中,您将拥有:
ideas.map{ Future.traverse(_){ idea =>
/*something that returns a Future[JsObject]*/
} }.flatMap(identity)
但是,很多时候当你运行flatMap(标识)时,你可能会把地图变成一个flatMap,这就是这里的情况:
ideas.flatMap{ Future.traverse(_) { idea =>
/*something that returns a Future[JsOjbect]*/
} }
答案 1 :(得分:4)
Akka documentation对如何处理期货成分有一个很好的概述。一般来说,它概述了scala.concurrent.Future中的四种方法,可用于将期货的组合减少为单个Future实例:
Future.sequence
Future.traverse
Future.fold
Future.reduce