当函数依赖于未来的某些结果时,我一次又一次地挣扎.这通常归结为像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)) } }
所以我认为Seq[Future[X]]
从findAll 返回一个将不会带来更好的性能,因为我必须等待结果.它是否正确?
简而言之:使用Future调用返回一个Sequence,使用结果的每个元素创建另一个Future调用,以不会发生阻塞情况的方式将结果返回给异步操作.
你应该知道的Future伴侣对象上的两个方便的功能可以帮到这里,第一个,更容易包裹你的头Future.sequence
.它需要一个未来的序列并返回一个序列的未来.如果以a结束Future[Seq[Future[MyObject]]]
,那就打电话给那个result
.那么你可以将其更改为一个Future[Future[Seq[MyObject]]]
带result.map(Future.sequence(_))
然后Future[Future[X]]
为任何X 折叠一个,你可以运行"result.flatMap(identity)",实际上,你可以为任何M[M[X]]
创建一个M[X]
只要M
有flatMap
.
这里另一个有用的功能是Future.traverse
.它基本上是将a Seq[A]
映射到a Seq[Future[B]]
,然后运行Future.sequence得到一个Future[Seq[B]]
So的结果,在你的例子中,你有:
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]*/ } }