情景:
我有两个不同的Api调用(在网络上)。 ApiCall1,ApiCall2。最终ApiCall1将返回选项[目录],ApiCall2将返回Seq [目录]
然后我需要使用这两个并构建一个FrontPage对象。在FrontPage对象的实例化期间,它创建一个Seq [NewProducts]。每当它生成NewProduct时,NewProduct也必须在Future中的Web上调用MongoDB。在将FrontPage对象移交给视图之前,必须完成每个Future。
以下是FrontPage类的代码:
case class FrontPage(maybeCat1: Option[Catalogue], maybeCat2: Seq[Catalogue]) {
val newProducts:Seq[NewProduct] = {
maybeCat2.map( { cat =>
NewProduct(cat)
})
}
}
以下是NewProduct类的代码:
case class NewProduct(cat:Catalogue) {
val indivProduct:Option[IndivProduct] = {
// ???
// This next line goes out to Mongo and returns a Future[List[JsObject]]
val indiv:Future[List[JsObject]] = MongoFetch.getIndivProduct(cat)
//need to strip out the 'Future', wait for it to return?
val listJS = indiv .. ???? // <-- need just the List[JsObject]]
return IndivProduct(listJs) // <-- constructs a new Option[IndivProduct]
}
}
以下是控制器的代码:
def landing() = Action.async {
for {
catalogue1 <- models.Granite.getCatalogue("front-page") // <- ApiCall1
catalogue2 <- models.Granite.getCatalogue("tags") // <- ApiCall2
} yield {
//??? How to now build the FrontPage object
// surely it also depends on the future?
val fp = FrontPage(catalogue1, catalogue2)
Ok(views.html.frontpage.landing(fp)) // <- at this point all futures must have returned.
}
}
我真的希望能够将一个漂亮的整洁FrontPage对象传递给View(以及设计者),并在其上定义一组非常简单的函数供他们使用。所有的期货都必须回归。 Catalogue1和Catalogue2不依赖于任何东西甚至彼此。在FrontPage对象中创建Seq [NewProducts]取决于它们返回的两个。然后,我无法将FrontPage对象传递给视图,直到它从Mongo返回NewProducts。
这种复杂程度高于我习惯的程度。我对何时何地使用/ yield理解感到困惑。我担心这会以某种方式阻止因为期货太过嵌入案例类,在案例类中。控制器的最高级别包含在异步中,这是否意味着该异步调用中的任何和所有Futures都将是非阻塞的?
答案 0 :(得分:3)
将期货视为获得完整首页的步骤,而不是其中的一部分,并考虑这些步骤的每个小部分。
例如,要构造NewProduct的实例,请创建一个与db对话并返回未来已完成的NewProduct实例的方法。
case class NewProduct(cat:Catalogue, indiv: Option[IndivProduct])
def newProductFor(cat: Catalogue): Future[NewProduct] =
for {
listJs <- MongoFetch.getIndivProduct(cat)
} yield NewProduct(cat, IndivProduct(listJs))
然后,您可以再次使用处理加载/未来的函数/方法创建首页:
case class FrontPage(
maybeCat1: Option[Catalogue],
maybeCat2: Seq[Catalogue],
newProducts: Seq[NewProduct])
def loadFrontPage: Future[FrontPage] =
for {
catalogue1 <- models.Granite.getCatalogue("front-page")
tags <- models.Granite.getCatalogue("tags")
newProducts <- loadNewProducts(tags)
} yield FrontPage(catalogue1, tags, newProducts)
def loadNewProducts(catalogues: Seq[Catalogue]): Future[Seq[NewProduct]] = {
Future.traverse(catalogues) { catalogue =>
newProductFor(catalogue)
}
}
注意Future.traverse采用A:s in的集合和一个来自A =&gt;的函数。未来[B]并返回Future [集合[B]]。
然后,您可以在异步控制器中调用它来提供给模板:
def page() = Action.async {
for {
frontPage <- loadFrontPage
} yield Ok(views.some.template(frontPage))
}