【问题标题】:Multiple Futures in Play and using case classes to hold the future dataPlay 中的多个 Futures 并使用案例类来保存未来数据
【发布时间】:2014-04-24 01:25:33
【问题描述】:

场景:

我有两个不同的 Api 调用(通过网络)。 ApiCall1,ApiCall2。最终 ApiCall1 将返回一个 Option[Catalogue] 而 ApiCall2 将返回一个 Seq[Catalogue]

然后我需要使用这两个并构建一个 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 对象传递给视图(以及设计师),并在其上定义一组非常简单的函数,供他们使用。所有的期货都必须回归。 Catalogue1 和 Catalogue2 不依赖于任何东西,甚至不依赖于彼此。在 FrontPage 对象中创建 Seq[NewProducts] 取决于它们都已返回。然后我不能将 FrontPage 对象传递给视图,直到它从 Mongo 返回 NewProducts。

这种复杂程度超出了我的习惯。我对何时何地使用 for/yield 理解感到困惑。恐怕这会以某种方式阻塞,因为 Futures 太嵌入在案例类中,在案例类中。控制器的最顶层封装在 Async 中,那么这是否意味着该 Async 调用中的所有 Future 都将是非阻塞的?

【问题讨论】:

  • 你能避免在你的构造函数中做这个工作吗?即,将其移至返回 Future[NewProduct]? 的构造函数方法
  • @TravisBrown - 问题是我真的想将一个完整的、完全完整的 FrontPage 传递给视图。

标签: scala playframework-2.0


【解决方案1】:

将未来视为获得完整首页的步骤,而不是其中的一部分,并思考这些步骤的每个小部分会是什么。

例如,要构造 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 的集合和一个从 A => Future[B] 出发并返回 Future[collection[B]] 的函数。

然后您可以在异步控制器中调用它以提供给模板:

 def page() = Action.async { 
   for {
     frontPage <- loadFrontPage
   } yield Ok(views.some.template(frontPage))
 }

【讨论】:

  • 非常感谢,这很有帮助。对于阅读本文的其他人来说只有一件事,在 loadNewProducts 中,我相信它应该是目录 => newProductFor(catalogue) 而不是 NewProduct.createFrom(catalogue)。我得到了这个工作!谢谢
猜你喜欢
  • 2018-06-19
  • 2011-09-07
  • 2013-02-09
  • 1970-01-01
  • 2018-01-12
  • 2020-06-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多