简体   繁体   English

播放/ Scala /期货:链接请求

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM