我在Scala和Play中一直在努力解决其中的一些概念。我想更新我的数据库,我想我需要将我的数据库更新功能包装在Future中,但我不知道如何返回所需的play.api.mvc.Result
。
我在Scala中有一个控制器,它返回一些响应:
def somePath = MyCustomAction.async(parse.tolerantJson) { implicit request =>
request.body.validate[MyObject].map { myObject =>
for {
getSomething <- getSomethingFuture
getSomethingElse <- getSomethingElseFuture
} yield {
if (getSomethingElse) {
if (getSomething)
updateMyDatabase(myObject)//perform database request
else
BadRequest("Invalid request")
}
} else {
// do some other things
}
}
}
private [this] def updateMyDatabase(myObject: MyObject) {
// do things to update the database
}
updateMyDatabase应该返回Result
吗?我想将它包装在Future中并检查它是否成功完成?如果我检查,那么我会在Result
方法上返回正确的Success
吗?
目前,我不了解如何构建这些内容或如何实际实现基于Future
的解决方案。
答案 0 :(得分:1)
您的updateMyDatabase函数应该返回一些无单位值,以告知它是否成功。数据库操作可以返回多个响应:
因此,Try [Boolean]将是处理所有这些场景的好类型。
private [this] def updateMyDatabase(myObject: MyObject): Try[Boolean] = {
// do things to update the database
}
我们现在可以匹配响应,并返回正确的结果类型。
updateMyDatabase(myObject) match {
case Failure(exception) => BadRequest
case Success(b) => if (b) Ok else BadRequest
}
由于getSomethingFuture和getSomethingElseFutures都返回Futures,因此您已经在Future的上下文中工作,并且不需要在Future中包装任何结果。 yield关键字将确保将yield体中的任何内容重新转换回Future。
现在你仍然需要处理getSomethingFuture或getSomethingElseFuture失败的情况。为此,您可以使用recover功能。所以你的最终代码看起来像这样:
(for {
getSomething <- getSomethingFuture
getSomethingElse <- getSomethingElseFuture
} yield {
// this code only executes if both futures are successful.
updateMyDatabase(myObject) match {
case Failure(exception) => BadRequest
case Success(b) => if (b) Ok else BadRequest
}
}) recover {
// Here you can match on different exception types and handle them accordingly.
// So throw a specific exception for each task if you need to handle their failures differently.
case e: GetSomethingFutureFailed => BadRequest
case e: GetSomethingElseFutureFailed => BadRequest
case _ => BadRequest
}
来自游戏documentation:请注意,您可能想要将您的阻止代码包装在期货中。这不会使它成为非阻塞,它只是意味着阻塞将在不同的线程中发生。您仍然需要确保您使用的线程池有足够的线程来处理阻塞。
还要确保指示控制器注入执行上下文,如下所示:
import scala.concurrent.ExecutionContext
class AsyncController @Inject() (...)(implicit exec: ExecutionContext)
答案 1 :(得分:0)
@ soote的答案有效,但让updateMyDatabase
返回未来的一个原因是,您可以使用Future#recover
统一错误处理。另一个原因是你可以在其他地方重用该方法,因为Play类需要在Future
中完成阻塞操作
如果updateMyDatabase
返回Future[_]
,您可以执行以下操作:
def somePath = MyCustomAction.async(parse.tolerantJson) { implicit request =>
request.body.validate[MyObject].map { myObject =>
val futResult = for {
getSomething <- getSomethingFuture
getSomethingElse <- getSomethingElseFuture
if getSomethingElse
if getSomething
_ <- updateMyDatabase(myObject)
} yield Ok("")
futResult.recover {
case e: Exception1 => BadRequest("bad request")
case e: Exception2 => BadRequest("blah")
case e => InternalServerError(e.getMessage)
}
}
}
private [this] def updateMyDatabase(myObject: MyObject): Future[Unit] = ???
您可能想知道如何更精细地处理错误。这是一种方式:
def somePath = MyCustomAction.async(parse.tolerantJson) { implicit request =>
request.body.validate[MyObject].map { myObject =>
val futResult = for {
getSomething <- getSomethingFuture
getSomethingElse <- getSomethingElseFuture
_ <- if(getSomethingElse && getSomething) {
updateMyDatabase(myObject)
} else {
Future.failed(new CustomException("Couldn't get something else"))
}
} yield Ok("")
futResult.recover {
case e: CustomException => BadRequest("failed to get something else")
case e: Exception2 => BadRequest("blah")
case e => InternalServerError(e.getMessage)
}
}
}
这是另一种方式:
def somePath = MyCustomAction.async(parse.tolerantJson) { implicit request =>
request.body.validate[MyObject].map { myObject =>
val futResult = for {
getSomething <- getSomethingFuture
getSomethingElse <- getSomethingElseFuture
result <- if(getSomethingElse && getSomething) {
updateMyDatabase(myObject).map(_ => Ok(""))
} else {
Future.successful(BadRequest("failed to get something else"))
}
} yield result
futResult.recover {
case e: Exception2 => BadRequest("blah")
case e => InternalServerError(e.getMessage)
}
}
}