[英]Play / Scala / Futures: Chained Requests
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. 我正在尝试执行可能是一个简单的操作,但是却遇到了困难:我有一个Play控制器,该控制器在Mongo中创建了一个用户,但是我首先要确认是否已经有一个用户使用相同的电子邮件地址。 I have a function on my User object that searches for a User by email address and returns a Future[Option[User]]:
我的User对象上有一个函数,该函数通过电子邮件地址搜索User并返回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))
}
But when I modify the post method to first check for a User with the specified email it fails: 但是,当我修改post方法以首先检查具有指定电子邮件的User时,它失败了:
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))
}
It reports that while it expects a Future[Result], it found a Future[Object]. 它报告说,虽然它期望Future [Result],但找到了Future [Object]。 I think the error means that it ultimately found a Future[Future[Result]], which is not what it expects.
我认为该错误意味着它最终找到了Future [Future [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?
我应该添加Await.result()调用以等待第一个操作完成后再继续吗? Will that cause any unwanted synchronous operations to occur?
这会导致发生不需要的同步操作吗? Or is there a better way to approach this problem?
还是有更好的方法来解决此问题?
Thanks in advance! 提前致谢!
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
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
. 首先 ,
create(user).map { ... }
返回Future[Result]
,但是case u => BadRequest
返回Result
,则编译器转为更“宽”的类型,即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("")
}
}
Now, it is clear that both case blocks must return the same type: 现在,很明显,两个case块必须返回相同的类型:
val future: Future[Future[Result]] = findByEmail("").map {
case Some(u) => Future.successful(BadRequest)
case None => create(User()).map {
case u => Created("")
}
}
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 . 请注意,我是如何从
case Some(u) => BadRequest
更改为case Some(u) => Future.successful(BadRequest)
,现在我们有了Future[Future[Result]]
,它不是我们想要的,并显示了第二个问题 。 Let's see the Future.map
signature: 让我们看看
Future.map
签名:
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]
So, we receive a block that transforms from T
to S
and we then wrap S
into a Future
: 因此,我们收到一个从
T
转换为S
的块,然后将S
包装到Future
:
val futureInt: Future[Int] = Future.successful(1)
val futureString: Future[String] = futureInt.map(_.toString)
But what if the block returns another Future
? 但是,如果该区块返回另一个
Future
怎么办? Then it will be wrapped and you will get a Future[Future[...]]
: 然后将其包装,您将获得
Future[Future[...]]
:
val futureFuture: Future[Future[String]] = futureInt.map(v => Future.successful(v.toString))
To avoid the wrap, we need to use flatMap
instead of map
: 为了避免换行,我们需要使用
flatMap
而不是map
:
val futureInt: Future[Int] = Future.successful(1)
val futureString: Future[String] = futureInt.flatMap(v => Future.successful(v.toString))
Let's go back to your code and use a flatMap
instead: 让我们回到您的代码,改用
flatMap
:
val future: Future[Result] = findByEmail("").flatMap {
case Some(u) => Future.successful(BadRequest)
case None => create(User()).map {
case u => Created("")
}
}
And then, the final version will be: 然后,最终版本将是:
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))
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.