scala playframework-2.0 future

Deshazte de Scala Future nesting



playframework-2.0 (2)

Una y otra vez estoy luchando cuando una función se basa en algunos resultados futuros. Esto generalmente se reduce a un resultado como Future [Seq [Future [MyObject]]]

Para deshacerme de eso, ahora uso Esperar dentro de una función auxiliar para sacar un objeto que no es futuro y reducir el anidamiento.

Se parece a esto

def findAll(page: Int, perPage: Int): Future[Seq[Idea]] = { val ideas: Future[Seq[Idea]] = collection.find(Json.obj()) // [...] ideas.map(_.map { // UGLY? idea => { // THIS RETURNED A Future[JsObject] before val shortInfo: JsObject = UserDao.getShortInfo(idea.user_id) idea.copy(user_data = Some(shortInfo)) } }) }

Este código funciona pero a mi me parece bastante hacky. Las dos llamadas de mapas son otra falla. Pasé horas tratando de encontrar la forma de mantener esto completamente asíncrono y de devolver una sencilla Seq. ¿Cómo se puede resolver esto usando las mejores prácticas de Play2?

Editar Para que el caso de uso sea más claro:

Tengo un objeto A de mongodb (reactivemongo) y quiero agregar información proveniente de otra llamada a mongodb getShortInfo . Es un caso clásico de "obtener usuario para esta publicación" que se resolvería con una unión en RDBMS. getShortInfo naturalmente produciría un futuro debido a la llamada a la db. Para reducir el anidamiento dentro de findAll he usado Await (). ¿Es esta una buena idea?

findAll se llama desde una acción de reproducción asíncrona, se convierte en Json y se envía por cable.

def getIdeas(page: Int, perPage: Int) = Action.async { for { count <- IdeaDao.count ideas <- IdeaDao.findAll(page, perPage) } yield { Ok(Json.toJson(ideas)) } }

Así que creo que devolver un Seq[Future[X]] desde findAll no traerá un mejor rendimiento ya que tengo que esperar el resultado de todos modos. ¿Es esto correcto?

En pocas palabras, tome una llamada futura devolviendo una secuencia, use cada elemento del resultado para crear otra llamada futura, devuelva el resultado a una acción asíncrona de manera que no se produzcan situaciones de bloqueo.


Dos funciones útiles en el objeto compañero Futuro que debería saber podrían ayudar aquí, la primera, y más fácil de envolver su cabeza, es el Future.sequence . Toma una secuencia de futuros y devuelve un Futuro de una secuencia. Si están terminando con un Future[Seq[Future[MyObject]]] , llamemos a ese result . luego puede cambiar esto a Future[Future[Seq[MyObject]]] con result.map(Future.sequence(_))

Luego, para contraer un Future[Future[X]] para cualquier X, puede ejecutar "result.flatMap (identity)", de hecho, puede hacer esto para cualquier M[M[X]] para crear una M[X] siempre y cuando M tiene flatMap .

Otra función útil aquí es Future.traverse . Básicamente, es el resultado de tomar una Seq[A] , asignarla a una Seq[Future[B]] y luego ejecutar Future.sequence para obtener una Future[Seq[B]] En su ejemplo, tendría:

ideas.map{ Future.traverse(_){ idea => /*something that returns a Future[JsObject]*/ } }.flatMap(identity)

Sin embargo, muchas veces cuando ejecuta flatMap (identidad), podría estar convirtiendo un mapa en un flatMap, y este es el caso aquí:

ideas.flatMap{ Future.traverse(_) { idea => /*something that returns a Future[JsOjbect]*/ } }


La documentación de Akka tiene un buen resumen sobre cómo lidiar con una composición de futuros. En general, describe cuatro métodos en scala.concurrent.Future que se pueden usar para reducir la composición de futuros en una única instancia de futuro:

  • Future.sequence
  • Future.traverse
  • Future.fold
  • Future.reduce