scala future

scala - Futuros-mapa vs plano



future (3)

¿Asegurarse de que processFile siempre se ejecute en un Future incluso si no se asignó desde downloadFile ?

Si, eso es correcto.

Sin embargo, la mayoría de las veces no utilizaría Future { ... } directamente, usaría funciones (de otras bibliotecas o las suyas) que devuelven un Future .

Imagina las siguientes funciones:

def getFileNameFromDB{id: Int) : Future[String] = ??? def downloadFile(fileName: String) : Future[java.io.File] = ??? def processFile(file: java.io.File) : Future[ProcessResult] = ???

Podrías usar flatMap para combinarlos:

val futResult: Future[ProcessResult] = getFileNameFromDB(1).flatMap( name => downloadFile(name).flatMap( file => processFile(file) ) )

O usando una para la comprensión:

val futResult: Future[ProcessResult] = for { name <- getFileNameFromDB(1) file <- downloadFile(name) result <- processFile(file) } yield result

La mayoría de las veces no llamaría al onSuccess (o al onComplete ). Al utilizar una de estas funciones, registra una función de devolución de llamada que se ejecutará cuando finalice el Future .

Si en nuestro ejemplo desea representar el resultado del procesamiento del archivo, devolverá algo como Future[Result] lugar de llamar a futResult.onSuccess(renderResult) . En el último caso, su tipo de devolución sería Unit , por lo que realmente no puede devolver algo.

En Play Framework esto podría verse como:

def giveMeAFile(id: Int) = Action.async { for { name <- getFileNameFromDB(1) file <- downloadFile(name) processed <- processFile(file) } yield Ok(processed.byteArray).as(processed.mimeType)) }

He leído los documentos sobre map y flatMap y entiendo que flatMap se usa para una operación que acepta un parámetro Future y devuelve otro Future . Lo que no entiendo completamente es por qué querría hacer esto. Tomemos este ejemplo:

  1. El usuario golpea mi servicio web pidiendo "hacer cosas"
  2. Descargo un archivo (que es lento)
  3. Proceso el archivo (que es intensivo de CPU)
  4. Renderizar el resultado

Entiendo que me gustaría usar un futuro para descargar el archivo, pero tengo dos opciones para procesarlo:

val downloadFuture = Future { downloadFile } val processFuture = downloadFuture map { processFile } processFuture onSuccess { case r => renderResult(r) }

o

val downloadFuture = Future { // download the file } val processFuture = downloadFuture flatMap { Future { processFile } } processFuture onSuccess { case r => renderResult(r) }

Al agregar sentencias de depuración ( Thread.currentThread().getId ) veo que en ambos casos la descarga, el process y el render producen en el mismo hilo (utilizando ExecutionContext.Implicits.global ).

¿Utilizaría flatMap simplemente para desacoplar el flatMap de downloadFile y el processFile y asegurar que el processFile siempre se ejecuta en un Future incluso si no se asignó desde el archivo de downloadFile ?


Si tiene un futuro, digamos, Future[HttpResponse] , y desea especificar qué hacer con ese resultado cuando esté listo, como escribir el cuerpo en un archivo, puede hacer algo como responseF.map(response => write(response.body) . Sin embargo, si write también es un método asíncrono que devuelve un futuro, esta llamada de map devolverá un tipo como Future[Future[Result]] .

En el siguiente código:

import scala.concurrent.Future import scala.concurrent.ExecutionContext.Implicits.global val numF = Future{ 3 } val stringF = numF.map(n => Future(n.toString)) val flatStringF = numF.flatMap(n => Future(n.toString))

stringF es de tipo Future[Future[String]] mientras que flatStringF es de tipo Future[String] . La mayoría estaría de acuerdo, el segundo es más útil. Por lo tanto, Flat Map es útil para componer múltiples futuros juntos

Cuando se usa for comprensiones con Futuros, el flatMap bajo el capó se usa junto con el map .

import scala.concurrent.{Await, Future} import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ val threeF = Future(3) val fourF = Future(4) val fiveF = Future(5) val resultF = for{ three <- threeF four <- fourF five <- fiveF }yield{ three * four * five } Await.result(resultF, 3 seconds)

Este código dará 60.

Bajo el capó, Scala traduce esto a

val resultF = threeF.flatMap(three => fourF.flatMap(four => fiveF.map(five => three * four * five)))


def flatMap[B](f: A => Option[B]): Option[B] = this match { case None => None case Some(a) => f(a) }

Este es un ejemplo simple en el que la herramienta FlatMap funciona con la opción, esto puede ayudar a comprender mejor. En realidad, componerla no es agregar una envoltura otra vez. Eso es lo que necesitamos.