Scala Language
Futuros
Buscar..
Creando un futuro
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
object FutureDivider {
def divide(a: Int, b: Int): Future[Int] = Future {
// Note that this is integer division.
a / b
}
}
Simplemente, el método de divide
crea un Futuro que se resolverá con el cociente de a
sobre b
.
Consumiendo un futuro exitoso
La forma más fácil de consumir un futuro exitoso, o más bien, obtener el valor dentro del futuro, es usar el método de map
. Supongamos que un cierto código llama a la divide
método de la FutureDivider
objeto del ejemplo "Creando un futuro". ¿Cómo debería ser el código para obtener el cociente de a
sobre b
?
object Calculator {
def calculateAndReport(a: Int, b: Int) = {
val eventualQuotient = FutureDivider divide(a, b)
eventualQuotient map {
quotient => println(quotient)
}
}
}
Consumiendo un futuro fallido
A veces, el cálculo en un futuro puede crear una excepción, lo que hará que el futuro falle. En el ejemplo de "Creación de un futuro", ¿qué sucede si el código de llamada pasó 55
y 0
al método de divide
? Lanzaría una ArithmeticException
después de tratar de dividir por cero, por supuesto. ¿Cómo se manejaría eso en consumir código? En realidad, hay un puñado de maneras de lidiar con los fracasos.
Manejar la excepción con recover
y coincidencia de patrones.
object Calculator {
def calculateAndReport(a: Int, b: Int) = {
val eventualQuotient = FutureDivider divide(a, b)
eventualQuotient recover {
case ex: ArithmeticException => println(s"It failed with: ${ex.getMessage}")
}
}
}
Maneje la excepción con la proyección failed
, donde la excepción se convierte en el valor del futuro:
object Calculator {
def calculateAndReport(a: Int, b: Int) = {
val eventualQuotient = FutureDivider divide(a, b)
// Note the use of the dot operator to get the failed projection and map it.
eventualQuotient.failed.map {
ex => println(s"It failed with: ${ex.getMessage}")
}
}
}
Poniendo el futuro juntos
Los ejemplos anteriores demostraron las características individuales de un futuro, manejando casos de éxito y fracaso. Generalmente, sin embargo, ambas características se manejan mucho más tersamente. Aquí está el ejemplo, escrito de una manera más ordenada y más realista:
object Calculator {
def calculateAndReport(a: Int, b: Int) = {
val eventualQuotient = FutureDivider divide(a, b)
eventualQuotient map {
quotient => println(s"Quotient: $quotient")
} recover {
case ex: ArithmeticException => println(s"It failed with: ${ex.getMessage}")
}
}
}
Secuenciación y travesía de futuros.
En algunos casos es necesario calcular una cantidad variable de valores en futuros separados. Suponga que tiene una List[Future[Int]]
, pero en su lugar debe procesarse una List[Int]
. Entonces la pregunta es cómo convertir esta instancia de la List[Future[Int]]
en un Future[List[Int]]
. Para este propósito existe el método de sequence
en el objeto compañero Future
.
def listOfFuture: List[Future[Int]] = List(1,2,3).map(Future(_))
def futureOfList: Future[List[Int]] = Future.sequence(listOfFuture)
En general, la sequence
es un operador comúnmente conocido dentro del mundo de la programación funcional que transforma F[G[T]]
en G[F[T]]
con restricciones a F
y G
Hay un operador alternativo llamado traverse
, que funciona de manera similar pero toma una función como un argumento adicional. Con la función de identidad x => x
como parámetro, se comporta como el operador de sequence
.
def listOfFuture: List[Future[Int]] = List(1,2,3).map(Future(_))
def futureOfList: Future[List[Int]] = Future.traverse(listOfFuture)(x => x)
Sin embargo, el argumento adicional permite modificar cada instancia futura dentro del listOfFuture
dado. Además, el primer argumento no necesita ser una lista de Future
. Por lo tanto es posible transformar el ejemplo de la siguiente manera:
def futureOfList: Future[List[Int]] = Future.traverse(List(1,2,3))(Future(_))
En este caso, la List(1,2,3)
se pasa directamente como primer argumento y la función de identidad x => x
se reemplaza con la función Future(_)
para envolver de manera similar cada valor de Int
en un Future
. Una ventaja de esto es que la List[Future[Int]]
intermedia List[Future[Int]]
puede omitirse para mejorar el rendimiento.
Combina Futuros Múltiples - Para Comprensión
La comprensión es una forma compacta de ejecutar un bloque de código que depende del resultado exitoso de múltiples futuros.
Con f1, f2, f3
tres Future[String]
que contendrán las cadenas one, two, three
respectivamente,
val fCombined =
for {
s1 <- f1
s2 <- f2
s3 <- f3
} yield (s"$s1 - $s2 - $s3")
fCombined
será un Future[String]
contiene la cadena one - two - three
una vez que todos los futuros se hayan completado con éxito.
Tenga en cuenta que aquí se supone un ExectionContext implícito.
Además, tenga en cuenta que para la comprensión es solo un azúcar sintáctico para un método flatMap, por lo que la construcción de objetos Futuros dentro del cuerpo eliminaría la ejecución concurrente de bloques de código incluidos en futuros y llevaría a un código secuencial. Lo ves en el ejemplo:
val result1 = for {
first <- Future {
Thread.sleep(2000)
System.currentTimeMillis()
}
second <- Future {
Thread.sleep(1000)
System.currentTimeMillis()
}
} yield first - second
val fut1 = Future {
Thread.sleep(2000)
System.currentTimeMillis()
}
val fut2 = Future {
Thread.sleep(1000)
System.currentTimeMillis()
}
val result2 = for {
first <- fut1
second <- fut2
} yield first - second
Valor encerrada por result1
objeto sería siempre negativo, mientras que result2
serían positivos.
Para obtener más detalles sobre la comprensión y el yield
en general, consulte http://docs.scala-lang.org/tutorials/FAQ/yield.html