我正在尝试执行可能是一个简单的操作,但遇到困难:我有一个Play控制器在Mongo中创建一个用户,但我首先要验证是否还没有用户具有相同的电子邮件地址。我的User对象上有一个函数,它通过电子邮件地址搜索用户并返回Future [Option [User]]:
def findByEmail(email: String): Future[Option[User]] = {
collection.find(Json.obj("email" -> email)).one[User]
}
通过电子邮件搜索用户的我的控制器功能:
def get(id: String) = Action.async {
User.findById(id).map {
case None => NotFound
case user => Ok(Json.toJson(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)
}
}
相应的控制器功能起作用:
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方法以首先检查具有指定电子邮件的用户时,它会失败:
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]。我认为错误意味着它最终找到了未来[未来[结果]],这不是它所期望的。
我的问题是:将这些呼叫链接在一起的最佳做法是什么?我应该在继续之前添加Await.result()调用以等待第一个操作完成吗?这会导致发生任何不需要的同步操作吗?或者有更好的方法来解决这个问题吗?
提前致谢!
答案 0 :(得分:4)
您的代码存在两个问题。只看这块了一段时间:
case None => create(user).map {
case u => Created("")
}
case u => BadRequest
首先,create(user).map { ... }
返回Future[Result]
,但case u => BadRequest
返回Result
,然后编译器转到更多&#34}。宽"类型,Object
。让我们分开这个块(改变只是为了说明我的观点):
val future: Future[Object] = findByEmail("").map {
case Some(u) => BadRequest
case None => create(User()).map {
case u => Created("")
}
}
现在,很明显两个案例块都必须返回相同的类型:
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
签名:
def map[S](f: T => S)(implicit executor: ExecutionContext): Future[S]
忘记隐式执行者,因为它与此讨论无关:
def map[S](f: T => S): Future[S]
因此,我们会收到一个从T
转换为S
的块,然后我们将S
换成Future
:
val futureInt: Future[Int] = Future.successful(1)
val futureString: Future[String] = futureInt.map(_.toString)
但是如果该块返回另一个Future
怎么办?然后它将被包装,你将获得Future[Future[...]]
:
val futureFuture: Future[Future[String]] = futureInt.map(v => Future.successful(v.toString))
为避免换行,我们需要使用flatMap
代替map
:
val futureInt: Future[Int] = Future.successful(1)
val futureString: Future[String] = futureInt.flatMap(v => Future.successful(v.toString))
让我们回到您的代码并改为使用flatMap
:
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))
}