我有两个不同的Api调用(通过网络). ApiCall1,ApiCall2.最终ApiCall1将返回一个Option [Catalog],ApiCall2将返回一个Seq [Catalog]
然后我需要采取这两个并构建一个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都将是非阻塞的?
解决方法
例如,要构造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 =>的函数.未来[B]并返回Future [集合[B]].
然后,您可以在异步控制器中调用它以提供给模板:
def page() = Action.async { for { frontPage <- loadFrontPage } yield Ok(views.some.template(frontPage)) }