2016-10-24 1 views
1

Ich habe Code in meiner Play-Framework-App, der eine JSON-Anfrage analysiert und sie verwendet, um die Daten eines Benutzers zu aktualisieren. Das Problem ist, dass ich eine Future[Result] zurückgeben muss, aber meine userDAO.update Funktion gibt eine Future[Int] zurück, also habe ich geschachtelte Zukünfte.Wie verschachtelte Zukunft in Scala loswerden?

Ich habe verwendet Await, die nicht sehr gut ist. Wie kann ich diesen Code umschreiben, um die verschachtelte Zukunft zu vermeiden?

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

aktualisieren:

Sod Gesetz: Kurz nach dieser Entsendung ich es hat geklappt:

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

Aber ist es eine elegantere Art und Weise? Es scheint, als ob ich ganz frei um Future() pfeffern würde, was wie ein Code-Geruch aussieht.

+0

Vermeiden Sie einfach die 'Zukunft {request.body.asJson}'. Dies wird sowieso in einem eigenen Thread ausgeführt. Vermeide auch 'Future (Stauts (xx))', weil das unnötig eine asynchrone Berechnung erzeugt. Was Sie wollen, ist 'Future.successful' – rethab

+0

@rethab Danke für den Tipp. Ich wusste nicht über 'Future.successful'. Aber die 'Zukunft {request.body.asJson}' war so, dass ich die async Aktion verwenden und die 'Zukunft' zurückgeben konnte, die mit dem Abrufen des Benutzers von der DB verbunden ist ... (also nur, um den Typprüfer glücklich zu machen) – jbrown

+0

ich auch entdeckte, dass play 'body parser' liefert, von denen einer eine' 400' zurückgibt, wenn er die Anfrage nicht nach JSON parsen kann. Tun Sie dies mit 'Action.async (parse.json)' anstatt nur 'Action.async', und das bedeutet, dass' request.body' ein 'JsValue' ist, der um eine weniger' Future' geht. – jbrown

Antwort

4

Verwenden Sie flatMap anstelle von map.

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

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

eleganteren Weg ist for comprehension

verwenden Verwendung Zum Verständnis Code wie diesen

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

Wenn ich ein "zum Verständnis" erwähne, glaube ich, dass es sich lohnt, ein Beispiel zu zeigen - dem "OP" zu helfen. Warum ist es auch sauberer, diesen Ansatz zu verwenden? –

+0

Ja bitte zeigen Sie ein Beispiel.Ich versuchte, es in ein Verständnis zu verwandeln, konnte aber nicht herausfinden, wie. Vielen Dank. – jbrown

+0

@jbrown bearbeitet die Antwort mit Beispiel – pamu

2

sieht aus, als @pamu sagte, es Ihren Code ein Bit löschen könnte, wenn Sie eine verwenden würden, zum Verständnis.

Ein anderer interessanter Ansatz (und rein in Bezug auf die funktionale Programmierung) wäre monad transformers (normalerweise ein ähnlicher Typ wie Future[Option[T]] Screams Monade Transformator) zu verwenden.

Sie sollten sich Bibliotheken wie cats (und oder scalaz) ansehen. Ich werde versuchen, eine kleine „Pseudo-Code“ Beispiel unter Verwendung von Katzen zu geben (weil ich spielen Rahmen nicht vor Ort habe):

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

Wie Sie sehen können, in diesem Fall der Monade Transformatoren ermöglichen, eine Art zu behandeln wie Future[Option[T]] als ein einzelner "Kurzschluss" -Typ (z. B. wird das Verständnis zu stoppen, wenn Sie entweder eine gescheiterte Zukunft haben, oder eine Zukunft, die keine enthält).

Verwandte Themen