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

   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."

FUTURES AREN’T ERSATZ THREADS

   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."

FUTURES AREN’T ERSATZ THREADS

Los  futures  sirven para componer acciones asíncronas y paralelas de forma fácil y razonable.

   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."

FUTURES AREN’T ERSATZ THREADS

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 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 mapflatMap, 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 (mapflatMap en Scala)
  • Otra para manejar el error (recoverrecoverWith 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 (mapflatMap en Scala)
  • Otra para manejar el error (recoverrecoverWith 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

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: mapflatMap, traverse/sequence, recover, recoverWith, etc...

Implementaciones en Scala

Futurice

Twitter

Todavía no son Open Source

Implementaciones en Scala

Futurice

Twitter

Todavía no son Open Source

No provee cacheo

Implementaciones en Scala

Soundcloud

Open Source

Preguntas, comentarios, correcciones

Fuentes

Futuros en java

Futuros (promesas) en JavaScript

Enlaces

Futures

By Miguel Vilá

Futures

  • 1,432