当一个函数依赖于一些 future 的结果时,我一次又一次地陷入困境。 这通常可以归结为像 Future[Seq[Future[MyObject]]] 这样的结果
为了摆脱这个问题,我现在在辅助函数中使用 Await 来获取非 future 对象并减少嵌套。
看起来像这样
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))
}
})
}
这段代码可以工作,但对我来说它看起来很老套。两次 map 调用是另一个缺陷。 我花了几个小时试图弄清楚如何保持完全异步并返回一个简单的 future Seq。如何使用 Play2 最佳实践来解决这个问题?
编辑 为了使用例更加清晰:
我有一个来自 mongodb (reactivemongo) 的对象 A,并且想要添加来自对 mongodb getShortInfo
的另一个调用的信息。这是一个经典的“获取此帖子的用户”案例,可以通过 RDBMS 中的联接来解决。
由于调用了数据库,getShortInfo
自然会产生一个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 调用,使用结果的每个元素创建另一个 Future 调用,以不应该发生阻塞情况的方式将结果返回到异步操作。
最佳答案
您应该知道的 Future 伴随对象上的两个方便的函数可以在这里提供帮助,第一个也是更容易理解的函数是 Future.sequence
。它需要一个 future 序列并返回一个序列的 Future。如果最终得到 Future[Seq[Future[MyObject]]]
,我们就调用该结果
。那么你可以将其更改为 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(identity) 时,您可能会将 map 转换为 flatMap,这里就是这种情况:
ideas.flatMap{ Future.traverse(_) { idea =>
/*something that returns a Future[JsOjbect]*/
} }
关于scala - 摆脱 Scala Future 嵌套,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20276872/