如何处理播放视图中隐式传递的期货

How to handle implicitly passed futures in play views

在我的特殊情况下,我有一个在所有页面上呈现的菜单。菜单内容使用 slick 从数据库加载并隐式传递给视图。整个事情看起来像这样:

控制器

class Application @Inject()(
  implicit val menuContext: MenuContext
) extends Controller {

  def index = Action.async {
    val content: Future[Content] = getContent
    content.map(c => Ok(views.html.index(c)))
  } 
}

菜单上下文

class MenuContext {
  val models: Future[List[SomeModel]] = getModelsFromDB
}

查看

@(content: Content)(implicit menuContext: MenuContext)
...
@menuContext.models // how to access my actual model and not the Future?
...

如何在我的视图中访问 List[SomeModel]?是否有一个 Action.async 等价物用于传递隐式参数?或者对于(几乎)所有视图中所需的内容,是否有更好的解决方案?

使模板必须处理 Future 绝对不是一个好主意 - 所以问题变成了你评论中的问题 - 如何 非阻塞 (?) 从您的异步内容源获取内容,以及从您的其他异步内容源获取您的菜单项?

对两个 Future 实例的 for 理解就可以了:

def index = Action.async { 
  val fContent:Future[Content] = getContent
  val fMenus:Future[List[SomeModel] = getModelsFromDB

  for {
    content <- fContent
    menus <- fMenus
  } yield(Ok(views.html.index(content)(menus))))
} 

注意:您可能想尝试节省几行并将方法调用(getContentgetModelsFromDB)直接放入for块。 不幸的是,虽然它会编译 工作,但这两个任务 不会 运行 并行,从而使练习有些徒劳.

好的,我在这里添加另一个答案,专门尝试 DRY up 将菜单​​注入到您的操作中。

主要问题是您需要在恰到好处的时间注入菜单,即:

  • 当您准备好数据时(或至少 Future 持有数据)
  • 当您知道要呈现哪个模板时
  • 当您知道您将返回什么状态代码时

由于这些限制,我们不能使用 ActionBuilder or ActionRefiner - 他们假设您的内部控制器代码块将生成完成的 Result.

因此,我们将定义一个可以混合到控制器中的特征:

trait MenuDecoration {

  def withMenuSimple(body: Future[List[SomeModel] => Result]):Future[Result] = {
    val fm = getModelsFromDB
    val fb = body
    for {
      m <- fm
      b <- fb
    } yield(b(m))
  }
}

从我的另一个回答来看,这应该看起来很熟悉,并且它的工作方式相同 - 它将开始执行两个异步任务,并在它们完成后将它们放在一起。

需要用菜单装饰模板的Action如下所示:

class BlahController extends Controller with MenuDecoration {

  def index = Action.async {
    withMenuSimple {
      getContent.map { content => implicit menu =>
        Ok(views.html.index(content))
      }
    }
  }
}

为什么 withMenuSimple?因为在某些时候你可能想要检查 Request - 所以我们有这个选择:

trait MenuDecoration {
   ...

  def withMenu(body: RequestHeader => Future[List[SomeModel] => Result])(implicit request:RequestHeader):Future[Result] = {
    val fm = fMenus
    val fb = body(request)
    for {
      m <- fm
      b <- fb
    } yield(b(m))
  }
}

您可以这样使用:

def indexWithReq = Action.async { implicit request =>
  withMenu { req =>
    getContent.map { content => implicit menu =>
      Ok(views.html.index(content))
    }
  }
}