scala - Futuros-mapa vs plano
future (3)
¿Asegurarse de que
processFile
siempre se ejecute en unFuture
incluso si no se asignó desdedownloadFile
?
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:
- El usuario golpea mi servicio web pidiendo "hacer cosas"
- Descargo un archivo (que es lento)
- Proceso el archivo (que es intensivo de CPU)
- 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.