Play中的多个期货和使用案例类来保存未来数据

时间:2014-03-18 11:33:28

标签: scala playframework-2.0

情景:

我有两个不同的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都将是非阻塞的?

1 个答案:

答案 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))
 }