控制器中的嵌套异步调用

时间:2016-03-02 01:50:51

标签: scala asynchronous playframework

我有一个这样的端点:

POST /user/:id/addData

控制器功能如下所示:

def addData(id: Int) = Action.async { implicit request =>

    // Async #1 - Make sure this user exists
    usersDAO.get(id).map(user => {
        is(user.isEmpty) {
            BadRequest("That user doesn't exist")
        } else {

            val body = request.body.asJson.get.as[JsObject]
            // Data processing here ...

            // Async #2 - Insert some data from the POST body
            (for {
                foo <- fooDAO.insert(fooData)
                bar <- barDAO.insert(barData)
            } yield (foo, bar)).map {
                case options => Ok("Data was added!")
            }.recover {  // <-------------------------- Compilation error here
                case e => BadRequest(e)
            }
        }
    })
}

我收到编译时错误:

type mismatch;
 found   : scala.concurrent.Future[play.api.mvc.Result]
 required: play.api.mvc.Result

我认为错误是因为执行上下文在第一个异步调用(也就是一个Future)中,所以因为我正在进入另一个异步调用,就像我正在返回嵌套的Futures。

这样做的正确方法是什么?如果可能的话,我想取消嵌套这些调用(如Javascript中的Promises)。

2 个答案:

答案 0 :(得分:2)

您的问题与以下答案中解释的问题非常接近:

https://stackoverflow.com/a/35640546/4600

基本上,您要映射Future但在map内返回两种不同的类型:

if(user.isEmpty) {
  BadRequest("That user doesn't exist")
}

上面的if块返回Resultelse块返回Future[Result]。但要求它返回Result,以便map生成Future[Result]而不是Future[Future[Result]]

现在,解决起来非常简单:

  1. 首先,由于您在Future内使用异步调用(产生map),因此不应使用map,而应使用flatMap(请参阅回答下面链接到更多细节)。
  2. flatMap内的所有区块都必须返回Future[Result]
  3. 我们走了(见评论):

    def addData(id: Int) = Action.async { implicit request =>
    
      // See that we are now using a flatMap
      usersDAO.get(id).flatMap(user => {
        if(user.isEmpty) {
          // Return a future instead of a Result
          Future.successful(BadRequest("That user doesn't exist"))
        } else {
    
          val body = request.body.asJson.get.as[JsObject]
    
          // Just to be more explicity about the types and to
          // be easier to made comments below.
          val future: Future[(Foo, Bar)] = for {
            foo <- fooDAO.insert(fooData)
            bar <- barDAO.insert(barData)
          } yield (foo, bar)
    
          // This map returns a Future[Result] which is exactly what
          // out flatMap about expects.
          future.map {
            case options => Ok("Data was added!")
          }.recover { // This also returns a Future[Result] and now the compiler is happy
            case e => BadRequest(e)
          }
        }
      })
    }
    

答案 1 :(得分:1)

Result期望Future[Result]时,您的if / else会尝试在外map内同时返回Future#mapResult。最简单的方法是将usersDAO.get(id).map { ...更改为usersDAO.get(id).flatMap { ...并将BadRequest("That user doesn't exist")包裹在Future.successful(...)中。

由于所有这些方法似乎都会返回期货,所以你可以通过将它们全部放入for-understand中来使它更加优雅:

def addData(id: Int) = Action.async(parse.json) { implicit request =>
    (for {
        user <- usersDAO.get(id).filter(_.nonEmpty)
        body = request.body.as[JsObject]
        foo <- fooDAO.insert(fooData)
        bar <- barDAO.insert(barData)
    } yield {
        // user, body, foo, bar are in scope here
        Ok("Data was added!")
    }) recover {
        case _: NoSuchElementException => BadRequest("That user doesn't exist")
        case e => BadRequest(e)
    }
}

这可能无法立即编译,因为我不确定您的所有返回类型是什么。请注意,我还添加了BodyParser parse.json,因此您只需撰写request.body