播放/Scala/期货:链接请求 [英] Play / Scala / Futures: Chained Requests
问题描述
我正在尝试执行可能是一个简单的操作,但遇到了困难:我有一个Play控制器,该控制器在Mongo中创建了一个用户,但我首先要确认是否已经有一个用户使用相同的电子邮件地址.我的User对象上有一个函数,该函数通过电子邮件地址搜索User并返回Future [Option [User]]:
I am trying to perform what is probably a simple operation, but running into difficulties: I have a Play controller that creates a user in Mongo, but I first want to verify that there is not already a user with the same email address. I have a function on my User object that searches for a User by email address and returns a Future[Option[User]]:
def findByEmail(email: String): Future[Option[User]] = {
collection.find(Json.obj("email" -> email)).one[User]
}
通过电子邮件搜索用户的我的控制器功能有效:
My controller function that searches for a User by email works:
def get(id: String) = Action.async {
User.findById(id).map {
case None => NotFound
case user => Ok(Json.toJson(user))
}
}
我有一个创建用户的功能:
I have a function that creates a user:
def create(user:User): Future[User] = {
// Generate a new id
val id = java.util.UUID.randomUUID.toString
// Create a JSON representation of the user
val json = Json.obj(
"id" -> id,
"email" -> user.email,
"password" -> user.password,
"firstName" -> user.firstName,
"lastName" -> user.lastName)
// Insert it into MongoDB
collection.insert(json).map {
case writeResult if writeResult.ok == true => User(Some(id), user.email, user.password, user.firstName, user.lastName)
case writeResult => throw new Exception(writeResult.message)
}
}
并且相应的控制器功能起作用:
And the corresponding controller function works:
def post = Action.async(parse.json) {
implicit request =>
request.body.validate[User].map {
user => User.create(user).map {
case u => Created(Json.toJson(u))
}
}.getOrElse(Future.successful(BadRequest))
}
但是当我修改post方法以首先检查具有指定电子邮件的用户时,它失败:
But when I modify the post method to first check for a User with the specified email it fails:
def post = Action.async(parse.json) {
implicit request =>
request.body.validate[User].map {
user => User.findByEmail(user.email).map {
case None => User.create(user).map {
case u => Created(Json.toJson(u))
}
case u => BadRequest
}
}.getOrElse(Future.successful(BadRequest))
}
它报告说,虽然它期望一个Future [Result],但是却找到一个Future [Object].我认为该错误意味着它最终找到了Future [Future [Result]],这不是预期的结果.
It reports that while it expects a Future[Result], it found a Future[Object]. I think the error means that it ultimately found a Future[Future[Result]], which is not what it expects.
我的问题是:将此类调用链接在一起的最佳实践是什么?我应该添加Await.result()调用以等待第一个操作完成后再继续吗?这会导致发生不需要的同步操作吗?还是有更好的方法来解决这个问题?
My question is: what is the best practice for chaining such calls together? Should I add an Await.result() call to wait for the first operation to complete before proceeding? Will that cause any unwanted synchronous operations to occur? Or is there a better way to approach this problem?
提前谢谢!
推荐答案
您的代码有两个问题.只是在这个块上停留了一段时间:
There are two problems with your code. Looking just to this block for awhile:
case None => create(user).map {
case u => Created("")
}
case u => BadRequest
首先,create(user).map { ... }
返回一个Future[Result]
,但是case u => BadRequest
返回一个Result
,然后编译器将其转换为更宽"的类型,即Object
.让我们分开这个块(更改只是为了说明我的观点):
First, create(user).map { ... }
returns a Future[Result]
, but case u => BadRequest
returns a Result
, then the compiler goes to a more "wide" type, which is Object
. Let's separate this block (changes just to illustrate my point):
val future: Future[Object] = findByEmail("").map {
case Some(u) => BadRequest
case None => create(User()).map {
case u => Created("")
}
}
现在,很明显,两个case块必须返回相同的类型:
Now, it is clear that both case blocks must return the same type:
val future: Future[Future[Result]] = findByEmail("").map {
case Some(u) => Future.successful(BadRequest)
case None => create(User()).map {
case u => Created("")
}
}
请注意我是如何从case Some(u) => BadRequest
更改为case Some(u) => Future.successful(BadRequest)
的,现在我们有了Future[Future[Result]]
,这不是我们想要的,它显示了第二个问题.让我们看看Future.map
签名:
Notice how I've changed from case Some(u) => BadRequest
to case Some(u) => Future.successful(BadRequest)
and now we have Future[Future[Result]]
, which is not what we want and shows the second problem. Let's see the Future.map
signature:
def map[S](f: T => S)(implicit executor: ExecutionContext): Future[S]
忘记隐式执行器,因为它与本次讨论无关:
Forget about the implicit executor, because it is irrelevant for this discussion:
def map[S](f: T => S): Future[S]
因此,我们收到一个从T
转换为S
的块,然后将S
包装到Future
中:
So, we receive a block that transforms from T
to S
and we then wrap S
into a Future
:
val futureInt: Future[Int] = Future.successful(1)
val futureString: Future[String] = futureInt.map(_.toString)
但是,如果该块返回另一个Future
怎么办?然后将其包装,您将得到一个Future[Future[...]]
:
But what if the block returns another Future
? Then it will be wrapped and you will get a Future[Future[...]]
:
val futureFuture: Future[Future[String]] = futureInt.map(v => Future.successful(v.toString))
为避免自动换行,我们需要使用flatMap
而不是map
:
To avoid the wrap, we need to use flatMap
instead of map
:
val futureInt: Future[Int] = Future.successful(1)
val futureString: Future[String] = futureInt.flatMap(v => Future.successful(v.toString))
让我们回到您的代码并改用flatMap
:
Let's go back to your code and use a flatMap
instead:
val future: Future[Result] = findByEmail("").flatMap {
case Some(u) => Future.successful(BadRequest)
case None => create(User()).map {
case u => Created("")
}
}
然后,最终版本将是:
def post = Action.async(parse.json) { implicit request =>
request.body.validate[User].map { user =>
findByEmail(user.email) flatMap { // flatMap instead of map
case Some(u) => Future.successful(BadRequest) // wrapping into a future
case None => create(user).map {
case u => Created(Json.toJson(u))
}
}
}.getOrElse(Future.successful(BadRequest))
}
这篇关于播放/Scala/期货:链接请求的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!