我有一个这样的端点:
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)。
答案 0 :(得分:2)
您的问题与以下答案中解释的问题非常接近:
https://stackoverflow.com/a/35640546/4600
基本上,您要映射Future
但在map
内返回两种不同的类型:
if(user.isEmpty) {
BadRequest("That user doesn't exist")
}
上面的if
块返回Result
,else
块返回Future[Result]
。但要求它返回Result
,以便map
生成Future[Result]
而不是Future[Future[Result]]
。
现在,解决起来非常简单:
Future
内使用异步调用(产生map
),因此不应使用map
,而应使用flatMap
(请参阅回答下面链接到更多细节)。flatMap
内的所有区块都必须返回Future[Result]
。我们走了(见评论):
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#map
和Result
。最简单的方法是将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
。