2016-02-25 175 views
2

我想執行什麼可能是一個簡單的操作,但遇到困難:我有一個播放控制器,在Mongo中創建一個用戶,但我首先要驗證有沒有已經是具有相同電子郵件地址的用戶。我有一個搜索電子郵件地址的用戶,並返回一個Future [選項[用戶]我的用戶對象上的功能:玩/斯卡拉/期貨:鏈接請求

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

但是,當我修改後的方法首先檢查用戶與指定的電子郵件失敗:

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

據報道,雖然這需要一個未來[結果],它找到了一個未來[對象]。我認爲這個錯誤意味着它最終找到了一個未來[未來[結果]],這不是它期望的。

我的問題是:將這些呼叫鏈接在一起的最佳做法是什麼?我應該在繼續之前添加一個Await.result()調用以等待第一個操作完成嗎?是否會導致發生不需要的同步操作?還是有更好的方法來解決這個問題?

在此先感謝!

+1

當然'等待'會增加同步,這是不希望的。 「未來」內部存在異質價值。確保所有情況下的返回類型相同。 – cchantep

回答

4

您的代碼有兩個問題。展望只是此塊一段時間:

case None => create(user).map { 
    case u => Created("") 
} 
case u => BadRequest 

首先create(user).map { ... }返回Future[Result],但case u => BadRequest返回Result,則編譯器去一個更「寬」型,這是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塊,然後我們總結SFuture

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)) 
} 
+0

謝謝,它工作完美!我仍然不完全瞭解map和flatMap之間的區別,但經過充分的研究和閱讀後,我發現flatMap完美地滿足了我的用例。對你來說還有一個問題:有沒有更好的方法來解決這個問題,以便理解?我閱讀了解理解,可以使flatMap()調用更具可讀性並避免大量的嵌套。我正試圖重寫它,但任何指針將不勝感激! – lygado

+0

這是一個非常好的參考資料,可以說明我的地圖vs flatMap問題:[Play Framework:沒有線程池和回調地獄的異步I/O](https://engineering.linkedin.com/play/play-framework-異步IO-沒有線程池與回調地獄) – lygado