播放/Scala/期货:链接请求 [英] Play / Scala / Futures: Chained Requests

查看:100
本文介绍了播放/Scala/期货:链接请求的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试执行可能是一个简单的操作,但遇到了困难:我有一个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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆