我正在尝试使用递归的未来调用来获取具有一定深度的树。 Futures.flatMap提前返回,但递归的期货会在提前返回后继续完成。
我们有一个带边(父子关系)和实体(树中的节点)的图数据库。要检索树,我们有两个单独的调用:getEdges和getEntitiesFromChildEdges。两者都归还期货。
初始调用getEquipment需要返回Future。由于我最终需要回归未来,我不想添加一个人为的等待。
这是代码。首先调用getEquipment
。
getTree( rootEntities: Seq[Entity], parents: Seq[Entity], depth: Int): Future[Seq[Entity]] {
getEdges( parents).flatMap{ edges =>
getEntitiesFromChildEdges( edges).map{ children =>
children.foreach{ child => findParent( child).addChild( child) }
if( depth > 0) {
getTree( rootEntities, children, depth - 1)
logTheTree( "DEEPER", rootEntities)
rootEntities
} else {
logTheTree( "!DEEPER", rootEntities)
rootEntities
}
}
}
}
getEquipment: Future[Json]{
getRootEntities.flatMap{ rootEntities =>
getTree( rootEntities, rootEntities, 3).flatMap { returnedRootEntities =>
logTheTree( "FINAL", rootEntities)
Json.toJson( rootEntities)
}
}
}
首先调用getEquipment。这是日志结果。请注意,在完全构建树之前,FINAL
的日志会返回。
DEEPER Root
DEEPER One
FINAL Root
FINAL One
---
DEEPER Root
DEEPER One
DEEPER Two
DEEPER Three
!DEEPER Root
!DEEPER One
!DEEPER Two
!DEEPER Three
树遍历是广度优先而不是深度优先,以最小化getEntities调用。与问题无关,但确实有助于理解树遍历传递序列的原因。
最后,我知道我不必要地传入rootEntities,但是当getTree.flatMap没有跟returnedRootEntities =>
时,编译器会抱怨。实际getEquipment: Future
更复杂,但这是针对不同的问题。
答案 0 :(得分:3)
以下是基于我使用Promise
处理此基于Future
的递归的注释的示例。我简化了您的示例,因此代码更清晰,但仍然存在需要进行基于Future
递归的相同问题。看一看,希望您可以将其与您的代码联系起来:
import concurrent.Future
import scala.concurrent.Promise
import concurrent.ExecutionContext.Implicits._
import scala.concurrent.Await
import concurrent.duration._
case class Person(id:Long, name:String, children:Option[List[Long]])
object RecurseTest extends App{
val Everyone = Map(
1L -> Person(1, "grandpa", Some(List(2,3))),
2L -> Person(2, "dad", Some(List(4,5))),
3L -> Person(3, "uncle joe", Some(List(6,7))),
4L -> Person(4, "me", None),
5L -> Person(5, "sis", None),
6L -> Person(6, "cousin fred", None),
7L -> Person(7, "cousin mary", None)
)
val f = getFamilMembers
//Only using Await here to demonstrate the example. Do not use in real code
val result = Await.result(f, 5 seconds)
println(result)
def getFamilMembers():Future[List[String]] = {
var names:List[String] = List.empty
val prom = Promise[List[String]]
def recurse(ids:List[Long]){
ids.headOption match{
//This is the recursion exit point
case None => prom.success(names.reverse)
case Some(id) =>
getPerson(id) map{ p =>
names = p.name :: names
val childIds = p.children.getOrElse(List.empty)
recurse(ids.tail ++ childIds)
}
}
}
recurse(List(1))
prom.future
}
def getPerson(id:Long):Future[Person] = {
Future{
val p = Everyone.get(id).get
println("loaded: " + p.name)
p
}
}
}
答案 1 :(得分:1)
有人指出代码提前返回的原因是因为最里面的getTree()立即返回一个Future(Doh!)。它应该有一个地图,并在地图内部包含它下面的两行。
else
已更改为Future.successful,getEntitiesFromChildEdges( edges).map
已更改为flatMap
。
这是更新的代码。
getTree( rootEntities: Seq[Entity], parents: Seq[Entity], depth: Int): Future[Seq[Entity]] {
getEdges( parents).flatMap{ edges =>
getEntitiesFromChildEdges( edges).flatMap{ children =>
children.foreach{ child => findParent( child).addChild( child) }
if( depth > 0) {
// getTree needs to have a map and the results inside it.
getTree( rootEntities, children, depth - 1).map{ r =>
logTheTree( "DEEPER", rootEntities)
rootEntities
}
} else {
logTheTree( "!DEEPER", rootEntities)
Future.successful( rootEntities)
}
}
}
}
cmbaxter的回答显示了承诺:-)我将来会考虑使用这种技术。