jbrown jbrown - 1 month ago 4
Scala Question

How to get rid of nested future in scala?

I've got some code in my play framework app that parses a JSON request and uses it to update a user's data. The problem is that I need to return a

Future[Result]
, but my
userDAO.update
function returns a
Future[Int]
so I have nested futures.

I've resorted to using
Await
which isn't very good. How can I rewrite this code to avoid the nested future?

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


Update:

Sod's law: Just after posting this I worked it out:

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


But, is there a more elegant way? It seems like I'm peppering
Future()
around quite liberally which seems like a code smell.

Answer

Use flatMap instead of map.

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

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

More elegant way is to use for comprehension

Using For comprehension Code looks like this

 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