Futures
Agenda
- Intro
- API imperativo
- API de combinadores funcionales
- Cómo funcionan
- Algunas cosas de cuidado
Combinando futuros y actores- Futuros en Java
- Futuros (promesas) en JavaScript
- ¿Qué sigue?
Intro
A Future[T] represents a T-typed result, which may or may not be delivered at some time in the future, or which may fail altogether. Like real-world asynchronous operations, a future is always in one of 3 states: incomplete, completed successfully, or completed with a failure."
A Future[T] represents a T-typed result, which may or may not be delivered at some time in the future, or which may fail altogether. Like real-world asynchronous operations, a future is always in one of 3 states: incomplete, completed successfully, or completed with a failure."
Los futures sirven para componer acciones asíncronas y paralelas de forma fácil y razonable.
A Future[T] represents a T-typed result, which may or may not be delivered at some time in the future, or which may fail altogether. Like real-world asynchronous operations, a future is always in one of 3 states: incomplete, completed successfully, or completed with a failure."
Los futures sirven para componer acciones asíncronas y paralelas de forma fácil y razonable.
Son ideales para cómputos asíncronos que no requieren estado. Por ejemplo consultas externas. También pueden servir para ejecutar side effects asíncronamente.
Historia
Friedman y Wise introdujeron el término promesa en 1976 (The Impact of Applicative Programming on Multiprocessing)
Baker y Hewit (el mismo del modelo de actores) propusieron los futuros implícitos para hacer evaluación paralela en lenguajes funcionales. [The incremental garbage collection of processes (1977)]
Historia
Friedman y Wise introdujeron el término promesa en 1976 (The Impact of Applicative Programming on Multiprocessing)
Baker y Hewit (el mismo del modelo de actores) propusieron los futuros implícitos para hacer evaluación paralela en lenguajes funcionales. [The incremental garbage collection of processes (1977)]

Las primeras nociones de futuros implicaban que para usar el valor dentro del futuro este debía forzarse (bloquearse)
Historia
Friedman y Wise introdujeron el término promesa en 1976 (The Impact of Applicative Programming on Multiprocessing)
Baker y Hewit (el mismo del modelo de actores) propusieron los futuros implícitos para hacer evaluación paralela en lenguajes funcionales. [The incremental garbage collection of processes (1977)]

Las primeras versiones no hablaban de cómo crear nuevos futuros a partir de otros
Las primeras nociones de futuros implicaban que para usar el valor dentro del futuro este debía forzarse (bloquearse)
Historia

Aplicaciones en sistemas distribuidos para hacer RPC
Historia

Aplicaciones en sistemas distribuidos para hacer RPC

Finagle, la librería de Twitter para hacer RPC, esta cimentada en la composición de Futuros
-
Los Futuros no son una característica del lenguaje. Existen varias librerías que los implementan, de forma distinta:
- scala.concurrent.Future
- com.twitter.util.Future
- scalaz.Future / scalaz.Task
-
Los Futuros no son una característica del lenguaje. Existen varias librerías que los implementan, de forma distinta:
- scala.concurrent.Future <- El foco de esta charla
- com.twitter.util.Future
- scalaz.Future / scalaz.Task
- Para ejecutar futuros de la librería estándar se requiere tener un ExecutionContext, que es algo que ejecuta código típicamente en un pool de threads.
-
Los Futuros no son una característica del lenguaje. Existen varias librerías que los implementan, de forma distinta:
- scala.concurrent.Future <- El foco de esta charla
- com.twitter.util.Future
- scalaz.Future / scalaz.Task
- Para ejecutar futuros de la librería estándar se requiere tener un ExecutionContext, que es algo que ejecuta código típicamente en un pool de threads.
- Scala provee un contexto de ejecución global (que se utiliza haciendo import scala.concurrent.ExecutionContext.Implicits.global), pero para aplicaciones de verdad se sugiere tener varios aislados.
-
Los Futuros no son una característica del lenguaje. Existen varias librerías que los implementan, de forma distinta:
- scala.concurrent.Future <- El foco de esta charla
- com.twitter.util.Future
- scalaz.Future / scalaz.Task
Future { miCodigo() }
Para ejecutar un bloque de código dentro de un Future basta con hacer:
Future { miCodigo() }
Para ejecutar un bloque de código dentro de un Future basta con hacer:

Intuición
implicit val ec: ExecutionContext = ...
def ejecutarFuturo(): Future[Int] = Future {
Thread.sleep(2000)
println("Future completed!")
4+5
}
ejecutarFuturo()
println("Completed!")
¿Qué se imprime primero?:
Intuición
implicit val ec: ExecutionContext = ...
def ejecutarFuturo(): Future[Int] = Future {
Thread.sleep(2000)
println("Future completed!")
4+5
}
ejecutarFuturo()
println("Completed!")
¿Qué se imprime primero?:
> Completed!
> // suceden cerca de 2 segs
> Future completed!
Algunas ventajas
Tienen una API muy rica que permite:
Algunas ventajas
Tienen una API muy rica que permite:
- Despachar trabajo a otros threads
Algunas ventajas
Tienen una API muy rica que permite:
- Despachar trabajo a otros threads
- Paralelizar y unir resultados fácilmente
Algunas ventajas
Tienen una API muy rica que permite:
- Despachar trabajo a otros threads
- Paralelizar y unir resultados fácilmente
- Fácil de componer
Algunas ventajas
Tienen una API muy rica que permite:
- Despachar trabajo a otros threads
- Paralelizar y unir resultados fácilmente
- Fácil de componer
- Pueden documentar explícitamente (a través del tipo de retorno) que una función es asíncrona
Algunas desventajas
- No son adecuados para hacer modificaciones concurrentes sobre estado compartido
Algunas desventajas
- No son adecuados para hacer modificaciones concurrentes sobre estado compartido
- A diferencia de los actores no tiene sentido modelar aspectos del dominio con Futuros. Son mas "operacionales"
Algunas desventajas
- A diferencia de los actores no tiene sentido modelar aspectos del dominio con Futuros. Son mas "operacionales"
- Es fácil usarlos ignorando los recursos que consumen
- No son adecuados para hacer modificaciones concurrentes sobre estado compartido
API imperativo
API imperativo
class Future[T] {
def isCompleted: Boolean
def value: Option[Try[T]]
}
El resultado de estas funciones depende de cuando se llamen:
API imperativo
class Future[T] {
def isCompleted: Boolean
def value: Option[Try[T]]
}
El resultado de estas funciones depende de cuando se llamen:
None // <- No ha terminado
Some(Success(t)) // <- Terminó exitosamente con valor t
Some(Failure(throwable)) // <- Terminó fallidamente con un throwable
value puede retornar uno de tres posibles resultados (dependiendo del momento en el que se llamen):
API imperativo
class Future[T] {
def isCompleted: Boolean
def value: Option[Try[T]]
}
None // <- No ha terminado
Some(Success(t)) // <- Terminó exitosamente con valor t
Some(Failure(throwable)) // <- Terminó fallidamente con un throwable
El resultado de estas funciones depende de cuando se llamen:
value puede retornar uno de tres posibles resultados (dependiendo del momento en el que se llamen):
Estas funciones NO deberían usarse.
Para "manipular" el valor del futuro hay otras
API imperativo
class Future[T] {
def onComplete[U](f: (Try[T]) => U)(implicit e: ExecutionContext): Unit
def onSuccess[U](pf: PartialFunction[T, U])(implicit e: ExecutionContext): Unit
def onFailure[U](pf: PartialFunction[Throwable, U])(implicit e: ExecutionContext): Unit
}
Funciones de callbacks:
Sirven para mandar a ejecutar cierto pedazo de código una vez se haya resuelto el Futuro con éxito o falla
(onSuccess y onFailure están implementados reusando onComplete)
API imperativo
class Future[T] {
def onComplete[U](f: (Try[T]) => U)(implicit e: ExecutionContext): Unit
def onSuccess[U](pf: PartialFunction[T, U])(implicit e: ExecutionContext): Unit
def onFailure[U](pf: PartialFunction[Throwable, U])(implicit e: ExecutionContext): Unit
}
Funciones de callbacks:
¿Por qué son funciones imperativas?
API imperativo
class Future[T] {
def onComplete[U](f: (Try[T]) => U)(implicit e: ExecutionContext): Unit
def onSuccess[U](pf: PartialFunction[T, U])(implicit e: ExecutionContext): Unit
def onFailure[U](pf: PartialFunction[Throwable, U])(implicit e: ExecutionContext): Unit
}
Funciones de callbacks:
¿Por qué son funciones imperativas?
¿Si transformo el valor del futuro puedo retornar el resultado fácilmente?
¿Si transformo el resultado haciendo otra llamada asíncrona puedo retornar ése resultado?
API imperativo
class Future[T] {
def onComplete[U](f: (Try[T]) => U)(implicit e: ExecutionContext): Unit
def onSuccess[U](pf: PartialFunction[T, U])(implicit e: ExecutionContext): Unit
def onFailure[U](pf: PartialFunction[Throwable, U])(implicit e: ExecutionContext): Unit
}
Funciones de callbacks:
¿Por qué son funciones imperativas?
¿Si transformo el valor del futuro puedo retornar el resultado fácilmente?
¿Si transformo el resultado haciendo otra llamada asíncrona puedo retornar ése resultado?
Funciones que no retornan nada (Unit) no son una buena base para la composición
API de combinadores funcionales
map, flatMap, recover, recoverWith, sequence/traverse
map
- map transforma el resultado del Future. Cuando el Future sobre el que se llama map termine se ejecuta la función.
- Es una mejor forma de mandar a ejecutar una función después de una acción asíncrona que usando callbacks.
- La firma de map es algo como:
class Future[A] {
def map[B](f: A => B)(implicit ec: ExecutionContext): Future[B] = {
...
}
}
map

Componiendo acciones asíncronas
¿Qué pasa si uno quiere empezar con algo asíncrono y necesita continuar con el resultado de otra acción que también es asíncrona?
Componiendo acciones asíncronas
¿Qué pasa si uno quiere empezar con algo asíncrono y necesita continuar con el resultado de otra acción que también es asíncrona?
def userTrackIds(userId: UserId): Future[List[TrackId]] = { ... }
def tracks(trackIds: List[TrackId]): Future[List[Track]] = { ... }
def userTracks(userId: UserId): Future[List[Track]] = ???
Por ejemplo, ¿cómo obtener los tracks de un usuario dadas las siguientes funciones?:
Componiendo acciones asíncronas
¿Con map?
def userTracks(userId: UserId): Future[List[Track]] = {
userTrackIds(userId).map { trackIds =>
tracks(trackIds)
}
}
Componiendo acciones asíncronas
¿Con map?
def userTracks(userId: UserId): Future[List[Track]] = {
userTrackIds(userId).map { trackIds =>
tracks(trackIds)
} // <- NO COMPILA!!!!!!!!!!
}
error: type mismatch:
found: Future[Future[List[Track]]]
required: Future[List[Track]]
Componiendo acciones asíncronas
¿Con map?
def userTracks(userId: UserId): Future[List[Track]] = {
userTrackIds(userId).map { trackIds =>
tracks(trackIds)
} // <- NO COMPILA!!!!!!!!!!
}
error: type mismatch:
found: Future[Future[List[Track]]]
required: Future[List[Track]]
hmmm ¿Future[Future[List[Track]]]?
Componiendo acciones asíncronas
¿Con map?
def userTracks(userId: UserId): Future[List[Track]] = {
userTrackIds(userId).map { trackIds =>
tracks(trackIds)
} // <- NO COMPILA!!!!!!!!!!
}
error: type mismatch:
found: Future[Future[List[Track]]]
required: Future[List[Track]]
hmmm ¿Future[Future[List[Track]]]?
¿Una acción asíncrona que devuelve otra acción asíncrona?
Componiendo acciones asíncronas
¿Con map?
def userTracks(userId: UserId): Future[List[Track]] = {
userTrackIds(userId).map { trackIds =>
tracks(trackIds)
} // <- NO COMPILA!!!!!!!!!!
}
error: type mismatch:
found: Future[Future[List[Track]]]
required: Future[List[Track]]
hmmm ¿Future[Future[List[Track]]]?
¿Una acción asíncrona que devuelve otra acción asíncrona?
No tiene mucho sentido
Componiendo acciones asíncronas
¿Con map?
def userTracks(userId: UserId): Future[List[Track]] = {
userTrackIds(userId).map { trackIds =>
tracks(trackIds)
} // <- NO COMPILA!!!!!!!!!!
}
error: type mismatch:
found: Future[Future[List[Track]]]
required: Future[List[Track]]
hmmm ¿Future[Future[List[Track]]]?
¿Una acción asíncrona que devuelve otra acción asíncrona?
No tiene mucho sentido
¿Entonces?
flatMap
La firma de flatMap es es algo como:
class Future[A] {
def flatMap[B](f: A => Future[B])(implicit ec: ExecutionContext): Future[B] = {
...
}
}
Es decir, a flatMap se le debe pasar una función que devuelva un resultado asíncrono: otro Future
flatMap
La firma de flatMap es es algo como:
class Future[A] {
def flatMap[B](f: A => Future[B])(implicit ec: ExecutionContext): Future[B] = {
...
}
}
Es decir, a flatMap se le debe pasar una función que devuelva un resultado asíncrono: otro Future
def userTracks(userId: UserId): Future[List[Track]] = {
userTrackIds(userId).flatMap { trackIds =>
tracks(trackIds)
} // <- AHORA SI COMPILA!!!!!!!!!!
}
flatMap
def userTracks(userId: UserId): Future[List[Track]] = {
for {
trackIds <- userTrackIds(userId)
userTracks <- tracks(trackIds)
} yield userTracks
}
O usando un for-comprehension:
Pueden imaginar que `<-` es un operador dentro de los for-comprehension que sirve para "extraer" el valor del Futuro
for-comprehensions
Siendo e0 una expresión que es de tipo Future y e una expresión que usa p0:
e0.map { p0 => e }
Es lo mismo que:
for {
p0 <- e0
} yield e
for-comprehensions
e0.flatMap { p0 =>
e1.flatMap { p1 =>
e2.flatMap { p2 =>
...
en.map { pn => e }
}
}
}
Es lo mismo que:
for {
p0 <- e0
p1 <- e1
...
pn <- en
} yield e
- La expresión e2 puede utilizar el valor p1, e3 puede utilizar p1 y p2, etc ...
- La expresión e puede utilizar todos los valores p0, p1 ... pn
Para paralelismo
val f1: Future[Int] = ejecutarF1() // <- Se empieza a ejecutar
val f2: Future[String] = ejecutarF2() // <- Se empieza a ejecutar
val f3: Future[(Int,String)] = for {
n <- f1 // <- Se está ejecutando en paralelo a f2
s <- f2 // <- Se está ejecutando en paralelo a f1
} yield (n+1, "Hola,"+s) // Solo se ejecuta cuando f1 y f2 acaben
flatMap también se puede usar para reunir resultados de varios Futures que se ejecutan en paralelo:
Para paralelismo
val f1: Future[Int] = ejecutarF1() // <- Se empieza a ejecutar
val f2: Future[String] = ejecutarF2() // <- Se empieza a ejecutar
val f3: Future[(Int,String)] = for {
n <- f1 // <- Se está ejecutando en paralelo a f2
s <- f2 // <- Se está ejecutando en paralelo a f1
} yield (n+1, "Hola,"+s) // Solo se ejecuta cuando f1 y f2 acaben
flatMap también se puede usar para reunir resultados de varios Futures que se ejecutan en paralelo:
En cambio lo siguiente no se ejecutaría en paralelo:
val f3: Future[(Int,String)] = for {
n <- ejecutarF1() // <- Se empieza a ejecutar
s <- ejecutarF2() // <- Solo se empieza a ejecutar cuando se haya resuelto "n"
} yield (n+1, "Hola,"+s) // Solo se ejecuta cuando f1 y f2 acaben
recover / recoverWith
Sirven para convertir un error del futuro en un valor
recover / recoverWith
Sirven para convertir un error del futuro en un valor
recover: para transformar el error "síncronamente"

recover / recoverWith
Sirven para convertir un error del futuro en un valor
recover: para transformar el error "síncronamente"
recoverWith: para transformar el error "asíncronamente"


recover / recoverWith
Sirven para convertir un error del futuro en un valor
recover: para transformar el error "síncronamente"
recoverWith: para transformar el error "asíncronamente"


¿Qué quiere decir U >: T?
recover / recoverWith
Sirven para convertir un error del futuro en un valor
recover: para transformar el error "síncronamente"
recoverWith: para transformar el error "asíncronamente"


¿Qué quiere decir U >: T?
U es un supertipo de T.
recover / recoverWith
Sirven para convertir un error del futuro en un valor
recover: para transformar el error "síncronamente"
recoverWith: para transformar el error "asíncronamente"


¿Qué quiere decir U >: T?
U es un supertipo de T.
U es un tipo igual a T o más general que T
recover
trait FileExecutionResult
case class SuccessfulFileExecution(fileId: Int) extends FileExecutionResult
case class FailedFileExecution(
fileId: Int,
cause: FileExecutionException) extends FileExecutionResult
val f1: Future[SuccessfulFileExecution] = Future {
processFile(fileId)
SuccessfulFileExecution(fileId)
}
val f2 = f1.recover {
case fee: FileExecutionException => FailedFileExecution(fileId, fee)
}
recover
trait FileExecutionResult
case class SuccessfulFileExecution(fileId: Int) extends FileExecutionResult
case class FailedFileExecution(
fileId: Int,
cause: FileExecutionException) extends FileExecutionResult
val f1: Future[SuccessfulFileExecution] = Future {
processFile(fileId)
SuccessfulFileExecution(fileId)
}
val f2 = f1.recover {
case fee: FileExecutionException => FailedFileExecution(fileId, fee)
}
¿De qué tipo es f2?
recover
trait FileExecutionResult
case class SuccessfulFileExecution(fileId: Int) extends FileExecutionResult
case class FailedFileExecution(
fileId: Int,
cause: FileExecutionException) extends FileExecutionResult
val f1: Future[SuccessfulFileExecution] = Future {
processFile(fileId)
SuccessfulFileExecution(fileId)
}
val f2 = f1.recover {
case fee: FileExecutionException => FailedFileExecution(fileId, fee)
}
¿De qué tipo es f2?
Del primer tipo papá en común entre el caso exitoso y el del recover
recover
trait FileExecutionResult
case class SuccessfulFileExecution(fileId: Int) extends FileExecutionResult
case class FailedFileExecution(
fileId: Int,
cause: FileExecutionException) extends FileExecutionResult
val f1: Future[SuccessfulFileExecution] = Future {
processFile(fileId)
SuccessfulFileExecution(fileId)
}
val f2 = f1.recover {
case fee: FileExecutionException => FailedFileExecution(fileId, fee)
}
¿De qué tipo es f2?
Del primer tipo papá en común entre el caso exitoso y el del recover
Future[FileExecutionResult]
recoverWith
def getSomeResource(ipInstancia: String): Future[HttpResponse] = ???
val ipInstancia1 = ???
val ipInstancia2 = ???
val retriedResponse: Future[HttpResponse] = getSomeResource(ipInstancia1).recoverWith {
case _: TimeoutException => getSomeResource(ipInstancia2)
}
Permite usar un resultado asíncrono para recuperar un futuro fallido
Future.successful y Future.failed
Sirven para envolver una constante dentro de un Futuro exitóso o fallido
Future.successful y Future.failed
Sirven para envolver una constante dentro de un Futuro exitóso o fallido
def obtenerArgumentosServicio(): Future[Args] = ???
def consumirServicio(args: Args): Future[Resp] = ???
def argumentosValidos(args: Args): Boolean = ???
obtenerArgumentosServicio().flatMap( args =>
if(argumentosValidos(args))
consumirServicio(args)
else
Future.failed(new InvalidArgumentException("Argumentos inválidos"))
}
Future.successful y Future.failed
Sirven para envolver una constante dentro de un Futuro exitóso o fallido
Llamarlos no gasta un thread, a diferencia de Future { ... }


¿Cómo componer un número variable de Futuros?
class SamlLogoutNotificatorActor(notifier: SamlLogoutNotifier) extends Actor {
override def receive: Receive = {
case notifyLogout: NotifyLogout => {
val originalSender = sender
val currentSession = notifyLogout.currentServiceProviderSession
val sessions = notifyLogout.token.sessions
@volatile var sessionsResultCodes = List[SessionStatus.Value]()
def responseToSender() = blocking {
if (sessionsResultCodes.size == sessions.size) {
val allSessionsClosed = sessionsResultCodes.forall(sessionStatus => sessionStatus == SessionStatus.CLOSED)
originalSender ! LogoutNotified(allSessionsClosed)
}
}
implicit val ec: ExecutionContext = context.dispatcher
sessions.foreach(session => {
if (currentSession.spEntityId != session.spEntityId) {
notifier.notifyLogout(session, notifyLogout.encodedLogoutRequest)
.onComplete {
case Success(response) => {
if (response.getStatusCode == 200 || response.getStatusCode == 302) {
sessionsResultCodes = sessionsResultCodes :+ SessionStatus.CLOSED
} else {
sessionsResultCodes = sessionsResultCodes :+ SessionStatus.ACTIVE
}
responseToSender()
}
case Failure(t) => {
sessionsResultCodes = sessionsResultCodes :+ SessionStatus.ACTIVE
responseToSender()
}
}
} else {
sessionsResultCodes = sessionsResultCodes :+ SessionStatus.CLOSED
responseToSender()
}
})
}
}
}
¿Qué es lo que se quería hacer?
Después de haber intentado notificar en paralelo a todos los service providers, reunir todos los resultados.
¿Qué es lo que se quería hacer?
Después de haber intentado notificar en paralelo a todos los service providers, reunir todos los resultados.
¿Cómo podemos reunir los resultados de múltiples hilos en un sólo sitio?
¿Qué es lo que se quería hacer?
Después de haber intentado notificar en paralelo a todos los service providers, reunir todos los resultados.
¿Cómo podemos reunir los resultados de múltiples hilos en un sólo sitio?
¿Con un actor que tenga una lista y recibimos el resultado de cada hilo como un mensaje?
¿Qué es lo que se quería hacer?
Después de haber intentado notificar en paralelo a todos los service providers, reunir todos los resultados.
¿Cómo podemos reunir los resultados de múltiples hilos en un sólo sitio?
¿Con un actor que tenga una lista y recibimos el resultado de cada hilo como un mensaje?
No es necesario
¿Qué es lo que se quería hacer?
Buscamos una función con tipo:
List[Future[SessionStatus]] => Future[List[SessionStatus]]
¿Qué es lo que se quería hacer?
Buscamos una función con tipo:
List[Future[SessionStatus]] => Future[List[SessionStatus]]
¡La librería estándar ya la tiene!
def sequence[A](list: List[Future[A]])(implicit executor: ExecutionContext): Future[List[A]]
¿Qué es lo que se quería hacer?
Buscamos una función con tipo:
List[Future[SessionStatus]] => Future[List[SessionStatus]]
¡La librería estándar ya la tiene!
def sequence[A](list: List[Future[A]])(implicit executor: ExecutionContext): Future[List[A]]
(En realidad la firma es mucho más complicada):

class SamlLogoutNotificatorActor(notifier: SamlLogoutNotifier) extends Actor {
override def receive: Receive = {
case notifyLogout: NotifyLogout =>
val originalSender = sender
val currentSession = notifyLogout.currentServiceProviderSession
val sessions = notifyLogout.token.sessions
implicit val ec: ExecutionContext = context.dispatcher
val resultCodesFutures = sessions.map { session =>
if(currentSession.spEntityId != session.spEntityId) {
notifier.notifyLogout(session, notifyLogout.encodedLogoutRequest).map { response =>
if (response.getStatusCode == 200 || response.getStatusCode == 302) {
SessionStatus.CLOSED
} else {
SessionStatus.ACTIVE
}
} recover {
case t => SessionStatus.ACTIVE
}
} else {
Future.successful( SessionStatus.CLOSED )
}
}
Future.sequence(resultCodesFutures).onSuccess { sessionsResultCodes =>
val allSessionsClosed = sessionsResultCodes.forall {
status => status == SessionStatus.CLOSED
}
originalSender ! LogoutNotified(allSessionsClosed)
}
}
}
Con Future.sequence
Future.traverse
Future.traverse es una función hermana de Future.sequence
Sirve para mapear cada elemento de un iterable a un Futuro y reunir todos los Futuros en uno solo
Future.traverse
Future.traverse es una función hermana de Future.sequence
Sirve para mapear cada elemento de un iterable a un Futuro y reunir todos los Futuros en uno solo
Se podría implementar reusando Future.sequence así:
def traverse[A,B](l: List[A])(f: A => Future[B])(implicit ec: ExecutionContext): Future[List[B]] = {
sequence(l.map(f))
}
Future.traverse
Future.traverse es una función hermana de Future.sequence
Sirve para mapear cada elemento de un iterable a un Futuro y reunir todos los Futuros en uno solo
Se podría implementar reusando Future.sequence así:
Future.sequence también se puede implementar usando Future.traverse.
def traverse[A,B](l: List[A])(f: A => Future[B])(implicit ec: ExecutionContext): Future[List[B]] = {
sequence(l.map(f))
}
Future.traverse
Future.traverse es una función hermana de Future.sequence
Sirve para mapear cada elemento de un iterable a un Futuro y reunir todos los Futuros en uno solo
Se podría implementar reusando Future.sequence así:
Future.sequence también se puede implementar usando Future.traverse.
Las implementaciones de la librería estándar no reusan nada para evitar recorrer la lista más de una vez
def traverse[A,B](l: List[A])(f: A => Future[B])(implicit ec: ExecutionContext): Future[List[B]] = {
sequence(l.map(f))
}
class SamlLogoutNotificatorActor(notifier: SamlLogoutNotifier) extends Actor {
override def receive: Receive = {
case notifyLogout: NotifyLogout =>
val originalSender = sender
val currentSession = notifyLogout.currentServiceProviderSession
val sessions = notifyLogout.token.sessions
implicit val ec: ExecutionContext = context.dispatcher
Future.traverse(sessions) { session =>
if(currentSession.spEntityId != session.spEntityId) {
notifier.notifyLogout(session, notifyLogout.encodedLogoutRequest).map { response =>
if (response.getStatusCode == 200 || response.getStatusCode == 302) {
SessionStatus.CLOSED
} else {
SessionStatus.ACTIVE
}
} recover {
case t => SessionStatus.ACTIVE
}
} else {
Future.successful( SessionStatus.CLOSED )
}
}.onSuccess { sessionsResultCodes =>
val allSessionsClosed = sessionsResultCodes.forall {
status => status == SessionStatus.CLOSED
}
originalSender ! LogoutNotified(allSessionsClosed)
}
}
}
Con Future.traverse
def notifyLogout(
notifyLogout: NotifyLogout,
notifier: SamlLogoutNotifier)(implicit val ec: ExecutionContext): Future[Boolean] = {
currentSession = notifyLogout.currentServiceProviderSession
val sessions = notifyLogout.token.sessions
sessions.traverse { session =>
if(currentSession.spEntityId != session.spEntityId) {
notifier.notifyLogout(session, notifyLogout.encodedLogoutRequest).map { response =>
if (response.getStatusCode == 200 || response.getStatusCode == 302) {
SessionStatus.CLOSED
} else {
SessionStatus.ACTIVE
}
} recover {
case t => SessionStatus.ACTIVE
}
} else {
Future.successful( SessionStatus.CLOSED )
}
}.map { sessionsResultCodes =>
sessionsResultCodes.forall {
status => status == SessionStatus.CLOSED
}
}
}
Separado en una función
Cómo funcionan
Cómo funcionan
muy por encima
Los Futuros funcionan abstrayendo el encadenamiento de callbacks.
Cómo funcionan

La base de todo es onComplete
Cómo funcionan


La interfaz de ExecutionContext:
Cómo funcionan
- La implementación también utiliza Promesas (no confundir con promesas de JS)
Cómo funcionan
- La implementación también utiliza Promesas (no confundir con promesas de JS)
- Las promesas son variables que pueden ser resueltas (una sola vez) con un valor de tipo T o con un error.
Cómo funcionan
- La implementación también utiliza Promesas (no confundir con promesas de JS)
- Las promesas son variables que pueden ser resueltas (una sola vez) con un valor de tipo T o con un error.
- Las promesas y los futuros son dos caras de la misma moneda
- Las promesas permiten asignar un valor a un Futuro
- Los Futuros permiten leer ese valor y crear nuevos Futuros en función de él
Cómo funcionan

Otras funciones combinadoras utilizan onComplete y Promesas:
Cómo funcionan
Otras funciones combinadoras utilizan onComplete y Promesas:

Cómo funcionan
Otras funciones combinadoras utilizan onComplete y Promesas:

Si alguna vez han escrito un onComplete dentro de otro onComplete...
Cómo funcionan
Básicamente:
Cómo funcionan
Básicamente:
- Creando Runnables y mandándolos a ejecutar en el ExecutionContext
Cómo funcionan
Básicamente:
- Creando Runnables y mandándolos a ejecutar en el ExecutionContext
- Acumulando callbacks para ejecutarlos asíncronamente cuando se produzca un resultado
Cómo funcionan
Básicamente:
- Creando Runnables y mandándolos a ejecutar en el ExecutionContext
- Acumulando callbacks para ejecutarlos asíncronamente cuando se produzca un resultado
- Reusando onComplete con Promesas
¿De qué sirve saber esto?
¿De qué sirve saber esto?
- La lección es que onComplete/onFailure/onSuccess son funciones de "bajo" nivel.
¿De qué sirve saber esto?
- La lección es que onComplete/onFailure/onSuccess son funciones de "bajo" nivel.
- En cambio uno debería procurar usar combinadores funcionales:
- map
- flatMap
- recover
- recoverWith
- Future.sequence / Future.traverse
¿De qué sirve saber esto?
- La lección es que onComplete/onFailure/onSuccess son funciones de "bajo" nivel.
- En cambio uno debería procurar usar combinadores funcionales:
- map
- flatMap
- recover
- recoverWith
- Future.sequence / Future.traverse
- No está mal usar onComplete para side effects finales
Algunas cosas de cuidado
Algunas cosas de cuidado
Una lista no exhaustiva y en construcción de cosas a tener en cuenta al usarlos
Algunas cosas de cuidado
- No por que esté envuelto en un Futuro quiere decir que sea asíncrono
Algunas cosas de cuidado
- No por que esté envuelto en un Futuro quiere decir que sea asíncrono
- Despachar código bloqueante a un Futuro es mover el bloqueo a otro Thread.
Algunas cosas de cuidado
- No por que esté envuelto en un Futuro quiere decir que sea asíncrono
- Despachar código bloqueante a un Futuro es mover el bloqueo a otro Thread.
- Si el cliente necesita la respuesta del Futuro y el Futuro bloquea (en un único punto), entonces no hemos ganado nada.
Algunas cosas de cuidado
- No por que esté envuelto en un Futuro quiere decir que sea asíncrono
- Despachar código bloqueante a un Futuro es mover el bloqueo a otro Thread.
- Si el cliente necesita la respuesta del Futuro y el Futuro bloquea (en un único punto), entonces no hemos ganado nada.
-
Pero podemos utilizar los futuros para aliviar el bloqueo:
- Despachando a futuros cosas que no tenemos que devolver al cliente
- Paralelizando cosas y así liberando threads más rápidamente
Algunas cosas de cuidado
- Cuándo envuelvan código dentro de un Futuro asegurarse de que ése código termine eventualmente, aún con error.
Algunas cosas de cuidado
- Cuándo envuelvan código dentro de un Futuro asegurarse de que ése código termine eventualmente, aún con error.
scala> def f(): Future[Int] = Future {
| Thread.sleep(2100)
| println("Terminado!")
| 2
| }
f: ()scala.concurrent.Future[Int]
scala> Await.result(f(),2000 millis)
java.util.concurrent.TimeoutException: Futures timed out after [2000 milliseconds]
at scala.concurrent.impl.Promise$DefaultPromise.ready(Promise.scala:219)
at scala.concurrent.impl.Promise$DefaultPromise.result(Promise.scala:223)
at scala.concurrent.Await$$anonfun$result$1.apply(package.scala:116)
at scala.concurrent.BlockContext$DefaultBlockContext$.blockOn(BlockContext.scala:53)
at scala.concurrent.Await$.result(package.scala:116)
... 33 elided
Terminado!
scala>
- Cuando un Futuro se bloquea y devuelve un resultado NO necesariamente se está liberando el Thread, o los recursos, que estaba usando el Futuro:
Algunas cosas de cuidado
Hacer esto:
future.map(f).map(g).map(h)
Algunas cosas de cuidado
Hacer esto:
future.map(f).map(g).map(h)
¿es lo mismo que hacer esto?:
future.map(f andThen g andThen h)
Algunas cosas de cuidado
Hacer esto:
future.map(f).map(g).map(h)
¿es lo mismo que hacer esto?:
future.map(f andThen g andThen h)
Efectivamente van a devolver el mismo valor, incluso si alguna de las funciones lanza un error
Algunas cosas de cuidado
Hacer esto:
future.map(f).map(g).map(h)
¿es lo mismo que hacer esto?:
future.map(f andThen g andThen h)
Efectivamente van a devolver el mismo valor, incluso si alguna de las funciones lanza un error
¿Pero consumen los mismos recursos?
Algunas cosas de cuidado
Hacer esto:
future.map(f).map(g).map(h)
¿es lo mismo que hacer esto?:
future.map(f andThen g andThen h)
Efectivamente van a devolver el mismo valor, incluso si alguna de las funciones lanza un error
¿Pero consumen los mismos recursos?
future.map(f).map(g).map(h)
Manda a ejecutar asíncronamente f en un thread y una vez terminado manda a ejecutar asíncronamente g en otro thread y ...
Algunas cosas de cuidado
Hacer esto:
future.map(f).map(g).map(h)
¿es lo mismo que hacer esto?:
future.map(f andThen g andThen h)
Efectivamente van a devolver el mismo valor, incluso si alguna de las funciones lanza un error
¿Pero consumen los mismos recursos?
future.map(f).map(g).map(h)
Manda a ejecutar asíncronamente f en un thread y una vez terminado manda a ejecutar asíncronamente g en otro thread y ...
future.map(f andThen g andThen h)
Manda a ejecutar asíncronamente una sola función en un thread
Algunas cosas de cuidado
Hacer esto:
future.map(f).map(g).map(h)
¿es lo mismo que hacer esto?:
future.map(f andThen g andThen h)
Efectivamente van a devolver el mismo valor, incluso si alguna de las funciones lanza un error
¿Pero consumen los mismos recursos?
future.map(f).map(g).map(h)
Manda a ejecutar asíncronamente f en un thread y una vez terminado manda a ejecutar asíncronamente g en otro thread y ...
future.map(f andThen g andThen h)
Manda a ejecutar asíncronamente una sola función en un thread
3 momentos de asincronía
1 momento de asincronía
Algunas cosas de cuidado

¿Qué hace el Future de scalaz?
Algunas cosas de cuidado
Separar operaciones bloqueantes en un su propio pool de threads
We learned a few lessons along the way. The actors that syndicated content were initially configured to execute in a Future using Play’s default execution context. This meant any network anomaly could result in both syndication and user requests being blocked because the controllers also shared the same execution context. The solution was simple — have the Futures execute in a different execution context (...)
Given the fact that JDBC is blocking thread pools can be sized to the # of connections available to a db pool assuming that the thread pool is used exclusively for database access. Any lesser amount of threads will not consume the number of connections available. Any more threads than the number of connections available could be wasteful given contention for the connections.
¿Cómo debería ser el pool de threads de la base de datos?
Tener en cuenta como se integran los futuros
En Play Framework
Note: Both Action.apply and Action.async create Action objects that are handled internally in the same way. There is a single kind of Action, which is asynchronous, and not two kinds (a synchronous one and an asynchronous one). The.async builder is just a facility to simplify creating actions based on APIs that return a Future, which makes it easier to write non-blocking code.
No por que se esté usando Action.async se está ganando algo
Combinando Futuros y Actores
Combinando futuros y actores
Problema: modificar el estado de un actor según el resultado de un futuro
Combinando futuros y actores
class MiActor extends Actor {
def consulta(msg: Msg): Future[Response] = ???
def modificarEstado(response: Response): Unit = ???
def manejarError(error: Throwable) = ???
def receive: Receive = {
case msg: Msg =>
consulta(msg).onComplete {
case Success(response) => modificarEstado(response)
case Failure(error) => manejarError(error)
}
}
}
Problema: modificar el estado de un actor según el resultado de un futuro
Una aproximación inicial:
Combinando futuros y actores
class MiActor extends Actor {
def consulta(msg: Msg): Future[Response] = ???
def modificarEstado(response: Response): Unit = ???
def manejarError(error: Throwable) = ???
def receive: Receive = {
case msg: Msg =>
consulta(msg).onComplete {
case Success(response) => modificarEstado(response)
case Failure(error) => manejarError(error)
}
}
}
¿Qué pasa si llegan dos mensajes Msg? Uno va a ser procesado después del otro, pero los Futuros se podrían ejecutar concurrentemente
Combinando futuros y actores
class MiActor extends Actor {
def consulta(msg: Msg): Future[Response] = ???
def modificarEstado(response: Response): Unit = ???
def manejarError(error: Throwable) = ???
def receive: Receive = {
case msg: Msg =>
consulta(msg).onComplete {
case Success(response) => modificarEstado(response)
case Failure(error) => manejarError(error)
}
}
}
¿Qué pasa si llegan dos mensajes Msg? Uno va a ser procesado después del otro, pero los Futuros se podrían ejecutar concurrentemente
¡La idea de los actores es proteger el estado de modificaciones concurrentes!
Combinando futuros y actores
¿Qué hay de usar context.become?

Combinando futuros y actores
Una segunda aproximación (¡bloquear!):
Combinando futuros y actores
class MiActor extends Actor {
def consulta(msg: Msg): Future[Response] = ???
def modificarEstado(response: Response): Unit = ???
def manejarError(error: Throwable) = ???
val timeout: FiniteDuration = ???
def receive: Receive = {
case msg: Msg =>
val responseFuture = consulta(msg)
try {
val response = Await.result(responseFuture, timeout)
modificarEstado(response)
} catch {
case error => manejarError(error)
}
}
}
Una segunda aproximación (¡bloquear!):
Combinando futuros y actores
¿Es tan malo bloquear en este caso?
Combinando futuros y actores
¿Es tan malo bloquear en este caso?
Al fin y al cabo solo se puede modificar el estado hasta tener la respuesta del futuro
Combinando futuros y actores
¿Es tan malo bloquear en este caso?
¿Cuántos threads se están gastando por cada mensaje?
Al fin y al cabo solo se puede modificar el estado hasta tener la respuesta del futuro
Combinando futuros y actores
¿Es tan malo bloquear en este caso?
¿Cuántos threads se están gastando por cada mensaje?
Al fin y al cabo solo se puede modificar el estado hasta tener la respuesta del futuro
- El thread del actor no se puede liberar hasta que el Futuro se complete o produzca timeout
- El thread del Futuro
Combinando futuros y actores
¿Es tan malo bloquear en este caso?
¿Cuántos threads se están gastando por cada mensaje?
¿Se pueden gastar menos threads?
Al fin y al cabo solo se puede modificar el estado hasta tener la respuesta del futuro
- El thread del actor no se puede liberar hasta que el Futuro se complete o produzca timeout
- El thread del Futuro
Combinando futuros y actores
Complicando un poco las cosas:
Combinando futuros y actores
class MiActor extends Actor {
case class FSuccess(response: Response)
case class FFailure(error: Throwable)
def receive: Receive = {
case msg: Msg =>
consulta(msg).onComplete {
case Success(response) => self ! FSuccess(response)
case Failure(error) => self ! FFailure(error)
}
case FSuccess(response) =>
modificarEstado(response)
case FFailure(error) =>
manejarError(error)
}
}
Complicando un poco las cosas:
Combinando futuros y actores
class MiActor extends Actor with Stash {
case class FSuccess(response: Response)
case class FFailure(error: Throwable)
def receive: Receive = {
case msg: Msg =>
consulta(msg).onComplete {
case Success(response) => self ! FSuccess(response)
case Failure(error) => self ! FFailure(error)
}
become(esperandoFuturo)
}
def esperandoFuturo: Receive = {
case msg: Msg => stash()
case FSuccess(response) =>
modificarEstado(response); unstashAll(); become(receive)
case FFailure(error) =>
manejarError(error); unstashAll(); become(receive)
}
}
Si se necesita garantizar orden en el procesamiento de los mensajes:
Combinando futuros y actores
¿Cuántos threads se gastan en este caso?
Combinando futuros y actores
¿Cuántos threads se gastan en este caso?
Cuando llega un mensaje de tipo Msg:
Combinando futuros y actores
¿Cuántos threads se gastan en este caso?
Cuando llega un mensaje de tipo Msg:
- Si se recibe en receive se manda a ejecutar el futuro (1 thread) e inmediatamente se libera el thread del actor (no hay bloqueo)
- Si se recibe mientras se espera la respuesta de un futuro se guarda ese mensaje para después y se libera el thread del actor inmediatamente
Combinando futuros y actores
¿Cuántos threads se gastan en este caso?
Cuando llega un mensaje de tipo Msg:
- Si se recibe en receive se manda a ejecutar el futuro (1 thread) e inmediatamente se libera el thread del actor (no hay bloqueo)
- Si se recibe mientras se espera la respuesta de un futuro se guarda ese mensaje para después y se libera el thread del actor inmediatamente
Con esta solución, en un momento dado, se pueden estar gastando a lo sumo un solo thread (el del Futuro). (Esto ignorando ese pequeño momento en el que el actor está iniciando el Futuro)
Futuros en Java
Futuros en Java

Si representa una computación asíncrona...
... pero no tiene ninguno de los combinadores funcionales como map, flatMap, etc...
Futuros en Java

ListenableFuture de Guava
Futuros en Java





ListenableFuture de Guava
Futuros en Java
Una mejor alternativa. Futuros de Akka:

Futuros (promesas) en JavaScript
Futuros (promesas) en JavaScript
- En Javascript varios frameworks/librerías (Angular.js o ember.js por ejemplo) usan Promesas, que son muuuuy parecidas a Futuros en Scala (no confundir promesas en JS con promesas en Scala)
Futuros (promesas) en JavaScript
-
En JavaScript existe un estándar de promesas llamado Promises/A+ que es implementado por varias librerías:
- Angular utiliza una librería llamada $q
- Ember.js utiliza una llamada RSVP.js


- En Javascript varios frameworks/librerías (Angular.js o ember.js por ejemplo) usan Promesas, que son muuuuy parecidas a Futuros en Scala (no confundir promesas en JS con promesas en Scala)
promise
.then(doSomething) // `doSomething` puede ser una función "síncrona" --> map
.then(doSomethingElse); // `doSomethingElse` puede devolver una promesa --> flatMap
El método .then recibe 2 funciones (la segunda es opcional):
- Una para transformar el valor cuando la computación es exitosa (map y flatMap en Scala)
- Otra para manejar el error (recover y recoverWith en Scala)
promise
.then(null, handle) // `handle` puede ser una función "síncrona" --> recover
.then(null, handleOther); // `handleOther` puede devolver una promesa --> recoverWith
Futuros (promesas) en JavaScript
promise
.then(
function success(data) {
console.log(data);
})
.catch(function error(msg) {
console.error(msg);
});
promise
.then(
function success(data) {
console.log(data);
},
function error(msg) {
console.error(msg);
});
El método .then recibe 2 funciones (la segunda es opcional):
- Una para transformar el valor cuando la computación es exitosa (map y flatMap en Scala)
- Otra para manejar el error (recover y recoverWith en Scala)
Futuros (promesas) en JavaScript
$q.all({
foo: promiseFoo,
bar: promiseBar,
baz: promiseBaz
}).then(function(results) {
console.log(results.foo);
console.log(results.bar);
console.log(results.baz);
});
$q.all([promiseOne, promiseTwo, promiseThree])
.then(function(results) {
console.log(results[0], results[1], results[2]);
});
¡Incluso existe el equivalente de Future.sequence!:
También sirve sobre objetos cuyos atributos son promesas:
Futuros (promesas) en JavaScript

Blog post We have a problem with promises
Futuros (promesas) en JavaScript
¿Qué sigue?

Facebook's Haxl (2014)
Motivación:
- Aprovechar APIs que funcionan en batch.

Motivación:
- Aprovechar APIs que funcionan en batch.
- Evitar consultas repetidas:

Motivación:
- Aprovechar APIs que funcionan en batch.
- Evitar consultas repetidas:
- Paralelizar consultas a recursos distintos

Motivación:
- Aprovechar APIs que funcionan en batch.
- Evitar consultas repetidas:
Todo esto sin perder modularidad
- Paralelizar consultas a recursos distintos
¿Cómo?
Con una mónada que:
- Da la ilusión de que se hacen consultas individuales
- Da la ilusión de que siempre se hacen consultas sin importar si se hayan hecho antes
¿Cómo?
Con una mónada que:
- Da la ilusión de que se hacen consultas individuales
Pero por debajo se acumulan consultas individuales para usar un API en batch
- Da la ilusión de que siempre se hacen consultas sin importar si se hayan hecho antes
Pero por debajo se cachean consultas previas
¿Cómo utilizarlo?
A diferencia de los Futuros no se empezaría a ejecutar apenas uno lo define
- Primero hay una etapa de composición donde uno combina lógica de negocio para llegar a un único Fetch[A]
- Después hay una etapa de ejecución
En vez de un Future[A] uno busca construir un Fetch[A]
Para hacer esto basta utilizar un API que ya conocemos: map, flatMap, traverse/sequence, recover, recoverWith, etc...


Implementaciones en Scala
Futurice
Todavía no son Open Source


Implementaciones en Scala
Futurice
Todavía no son Open Source
No provee cacheo
Implementaciones en Scala

Soundcloud
Open Source
Preguntas, comentarios, correcciones
Fuentes
- Código fuente de la implementación de Futuros y Promesas en Scala:
- https://github.com/scala/scala/blob/v2.10.1/src/library/scala/concurrent/Promise.scala
- https://github.com/scala/scala/blob/v2.10.1/src/library/scala/concurrent/Future.scala
- https://github.com/scala/scala/blob/v2.10.1/src/library/scala/concurrent/impl/Promise.scala
- https://github.com/scala/scala/blob/v2.10.1/src/library/scala/concurrent/impl/Future.scala
Futuros en java
Futuros (promesas) en JavaScript
- Estándar Promises/A+
- Documentación de $q (Angular)
- From Callback (Future -> Functor -> Monad): Sobre cómo se podrían implementar Promesas/Futuros en JS
Enlaces
Futures
By Miguel Vilá
Futures
- 1,432