在同一个 Action.async Scala Play 中调用 2 个 Futures

Call 2 Futures in the same Action.async Scala Play

我是 Scala 的新手 :( 也就是说,我正在与 Play 框架 Action.async 和 Future 调用作斗争。

我想在同一个 Action 中调用 2 个 Futures 并等到它们都计算出来并在我的视图中发送它们的结果。

这是代码:

    def showPageWithTags(category: String) = Action.async {
        val page = PageDAO.findOne(Json.obj("category" -> category)).map {
          case Some(page) => {
            page.content = page.content.byteArrayToString
          }
        }
        val futureTags = ArticleDAO.listTags
        Ok(views.html.web.pages.show(page, futureTags))
   }

具有此定义的功能:

def findOne(id: String): Future[Option[PageModel]]

def listTags: Future[List[String]] 

我收到这个错误:

[error]  found   : Some[A]
[error]  required: models.PageModel
[error]         case Some(page) => {
[error]              ^
.../...
[error]  found   : None.type
[error]  required: models.PageModel
[error]         case None => {
[error]              ^
.../...
[error]  found   : Option[Nothing]
[error]  required: scala.concurrent.Future[play.api.mvc.Result]
[error]       optionPage.map {
[error]                      ^
.../...
[error]  found   : scala.concurrent.Future[Unit]
[error]  required: models.PageModel
[error]         Ok(views.html.web.pages.show(optionPage, futureTags))
[error]                                      ^
.../...
[error]  found   : scala.concurrent.Future[List[String]]
[error]  required: List[String]
[error]         Ok(views.html.web.pages.show(optionPage, futureTags))
[error]                                                  ^

我试过 map、for/yield、foreach 来处理 Option 和 Future,但总是会出现这样的错误之一。

然而,在我添加 "Tag" 功能之前,一个 Future 调用运行良好:

  def showPage(category: String) = Action.async {
    PageDAO.findOne(Json.obj("category" -> category)).map {
      case Some(page) => {
        page.content = page.content.byteArrayToString
        Ok(views.html.web.pages.show(page))
      }
      case None => {
        NotFound
      }
    }
  }

我如何在同一操作中调用 2 个 Futures 并等待它们都计算通过 Ok() 将它们传递到我的页面视图?

非常感谢任何澄清!

您需要 mapflatMap Future 才能异步访问它们的结果。 Action.async 需要 Future[Result],因此您必须将 Future 映射到那个。这是一个简单的方法:

def showPageWithTags(category: String) = Action.async {
    val page = PageDAO.findOne(Json.obj("category" -> category)).map {
      case Some(page) => {
        page.content.byteArrayToString
      }
    }
    val futureTags = ArticleDAO.listTags

    page.flatMap { page =>
        futureTags.map { tags =>
            Ok(views.html.web.pages.show(page, tags))
        }
    }
}

或者您可以使用 for-comprehension 来清理它,它是 flatMap/map.

的语法糖
def showPageWithTags(category: String) = Action.async {
    for {
        pageOpt <- PageDAO.findOne(Json.obj("category" -> category))
            .map(_.map(_.content.byteArrayToString))
        tags <- ArticleDAO.listTags
    } yield {
        pageOpt.map { page =>
            Ok(views.html.web.pages.show(page, tags))
        } getOrElse {
            NotFound
        }
    }
}

我还冒昧地在 findOne 结果上简化了您的 map

可以使用FutureflatMap方法。

The flatMap method takes a function that maps the value to a new future g, and then returns a future which is completed once g is completed.

(来自 http://docs.scala-lang.org/overviews/core/futures.html

也许可以试试:

def showPage(category: String) = Action.async {
  ArticleDAO.listTags.flatMap { tags =>
    PageDAO.findOne(Json.obj("category" -> category)).map {
      case Some(page) => {
        page.content = page.content.byteArrayToString
        Ok(views.html.web.pages.show(page, tags))
      }
      case None => {
        NotFound
      }
    }
  }

你可以这样试试

def showPageWithTags(category: String) = Action.async {

   for{
       pOpt <- PageDAO.findOne(Json.obj("category" -> category))
      futureTags <- ArticleDAO.listTags
      } yield{
          pOpt match {
           case Some(x)=> Ok(views.html.web.pages.show(x.content.byteArrayToString, futureTags))
           case _=> NotFound
                     }              
            }         
}