如何摆脱scala中的嵌套未来?

时间:2016-10-24 11:12:09

标签: scala playframework

我的play框架应用程序中有一些代码可以解析JSON请求并使用它来更新用户的数据。问题是我需要返回Future[Result],但我的userDAO.update函数返回Future[Int],所以我有嵌套的期货。

我使用的Await并不是很好。如何重写此代码以避免嵌套的未来?

def patchCurrentUser() = Action.async { request =>
Future {
  request.body.asJson
}.map {
  case Some(rawJson) => Json.fromJson[User](rawJson).map { newUser =>
    val currentUserId = 1

    logger.info(s"Retrieving users own profile for user ID $currentUserId")

    val futureResult: Future[Result] = userDAO.findById(currentUserId).flatMap {
      case Some(currentUser) =>
        val mergedUser = currentUser.copy(
          firstName = newUser.firstName        // ... and the other fields
        )

        userDAO.update(mergedUser).map(_ => Ok("OK"))
      case _ => Future { Status(404) }
    }

    import scala.concurrent.duration._
    // this is bad. How can I get rid of this?
    Await.result(futureResult, 1 seconds)
  }.getOrElse(Status(400))
  case _ => Status(400)
}
}

更新

索德定律:在发布之后我就把它解决了:

Future {
  request.body.asJson
}.flatMap {
  case Some(rawJson) => Json.fromJson[User](rawJson).map { newUser =>
    val currentUserId = 1
    userDAO.findById(currentUserId).flatMap {
      case Some(currentUser) =>
        val updatedUser = currentUser.copy(
          firstName = newUser.firstName
        )

        userDAO.update(updatedUser).map(_ => Ok("OK"))
      case _ => Future { Status(404) }
    }
  }.getOrElse(Future(Status(400)))
  case _ => Future(Status(400))
}

但是,有更优雅的方式吗?好像我在quite Future()周围充满了气味,这似乎是一种代码味道。

2 个答案:

答案 0 :(得分:4)

使用flatMap代替map

flatMap[A, B](f: A => Future[B])

map[A, B](f: A => B)

更优雅的方式是使用for comprehension

使用For comprehension代码如下所示

 for {
      jsonOpt <-  Future (request.body.asJson)
      result <- jsonOpt match {
        case Some(json) =>
          json.validate[User] match {
            case JsSuccess(newUser, _ ) =>
              for {
                currentUser <- userDAO.findById(1)
                _ <- userDAO.update(currentUser.copy(firstName = newUser.firstName))
              } yield Ok("ok")
            case JsError(_) => Future(Status(400))
          }
        case None => Future(Status(400))
      }
    } yield result

答案 1 :(得分:2)

正如@pamu所说,如果您使用for comprehension,它可能会清除您的代码。

另一种有趣的方法(在功能编程方面更纯粹)是使用monad transformers(通常类似于Future[Option[T]]类型的monad变换器)。

你应该看看像cats(和/或scalaz)这样的图书馆。我试着给出一个小的伪代码&#34;使用猫的例子(因为我没有在本地玩游戏框架):

import cats.data.OptionT
import cats.instances.future._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

def convertJsonToUser(json: Json): Future[Option[User]] = Json.fromJson[User](json)
def convertBodyToJson(request: Request): Future[Option[Json]] = Future {request.body.asJson}
def updateUser(user: User): Future[HttpResult] = Future {
  // update user
  Ok("ok")
}

def myFunction: Future[HttpResult] = {
  val resultOpt: OptionT[Future, HttpResult] = for {
    json <- OptionT(convertBodyToJson(request))
    user <- OptionT(convertJsonToUser(json))
    result <- OptionT.lift(updateUser(user))
  } yield result
  result.getOrElseF(Future {Status(400)})
}

正如您所看到的,在这种情况下,monad变换器允许将类似Future[Option[T]]的类型视为单个&#34;短路&#34;类型(例如,如果你有一个失败的未来,或者包含一个无的未来,那么理解就会停止。)