Play / Scala /期货:链式请求

时间:2016-02-25 21:51:56

标签: scala playframework

我正在尝试执行可能是一个简单的操作,但遇到困难:我有一个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()调用以等待第一个操作完成吗?这会导致发生任何不需要的同步操作吗?或者有更好的方法来解决这个问题吗?

提前致谢!

1 个答案:

答案 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))
}