Scala Language
Futures
Recherche…
Créer un avenir
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
}
}
Tout simplement, la méthode de la divide
crée un futur qui se résoudra avec le quotient d' a
over b
.
Consommer un avenir prospère
La manière la plus simple de consommer un futur réussi - ou plutôt d'obtenir la valeur dans le futur - consiste à utiliser la méthode de la map
. Supposons qu'un code appelle la méthode de divide
de l'objet FutureDivider
de l'exemple "Creating a Future". À quoi le code devrait-il ressembler pour obtenir le quotient d' a
over b
?
object Calculator {
def calculateAndReport(a: Int, b: Int) = {
val eventualQuotient = FutureDivider divide(a, b)
eventualQuotient map {
quotient => println(quotient)
}
}
}
Consommer un avenir défaillant
Parfois, le calcul dans un avenir peut créer une exception, ce qui entraînera l'échec du futur. Dans l'exemple "Creating a Future", que se passe-t-il si le code d'appel passe 55
et 0
à la méthode de divide
? Il lancerait une ArithmeticException
après avoir essayé de diviser par zéro, bien sûr. Comment cela serait-il géré en consommant du code? Il y a en fait une poignée de manières de traiter les échecs.
Gérer l'exception avec la recover
et la correspondance de modèle.
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}")
}
}
}
Traitez l'exception avec la projection failed
, où l'exception devient la valeur du futur:
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}")
}
}
}
Assembler l'avenir
Les exemples précédents ont mis en évidence les caractéristiques individuelles d’un futur, la gestion des cas de réussite et d’échec. Cependant, les deux fonctionnalités sont généralement beaucoup plus complexes. Voici l'exemple, écrit de manière plus nette et plus réaliste:
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}")
}
}
}
Séquençage et traversée des Futures
Dans certains cas, il est nécessaire de calculer un montant variable de valeurs sur des contrats à terme séparés. Supposons avoir une List[Future[Int]]
, mais au lieu de cela, une List[Int]
doit être traitée. Ensuite, la question est de savoir comment transformer cette instance de List[Future[Int]]
en Future[List[Int]]
. Pour cela, il existe la méthode de sequence
sur l'objet compagnon Future
.
def listOfFuture: List[Future[Int]] = List(1,2,3).map(Future(_))
def futureOfList: Future[List[Int]] = Future.sequence(listOfFuture)
En général, la sequence
est un opérateur communément connu dans le monde de la programmation fonctionnelle qui transforme F[G[T]]
en G[F[T]]
avec des restrictions sur F
et G
Il existe un autre opérateur appelé traverse
, qui fonctionne de manière similaire, mais prend une fonction comme argument supplémentaire. Avec la fonction d'identité x => x
le paramètre se comporte comme l'opérateur de sequence
.
def listOfFuture: List[Future[Int]] = List(1,2,3).map(Future(_))
def futureOfList: Future[List[Int]] = Future.traverse(listOfFuture)(x => x)
Cependant, l'argument supplémentaire permet de modifier chaque future instance dans la liste donnée listOfFuture
. De plus, le premier argument n'a pas besoin d'être une liste de Future
. Il est donc possible de transformer l'exemple comme suit:
def futureOfList: Future[List[Int]] = Future.traverse(List(1,2,3))(Future(_))
Dans ce cas, la List(1,2,3)
est transmise directement en tant que premier argument et la fonction d'identité x => x
est remplacée par la fonction Future(_)
pour envelopper de la même manière chaque valeur Int
dans un Future
. Un avantage de ceci est que la List[Future[Int]]
intermédiaire List[Future[Int]]
peut être omise pour améliorer les performances.
Combiner plusieurs contrats à terme - Pour la compréhension
Le pour la compréhension est un moyen compact d'exécuter un bloc de code qui dépend du résultat réussi de plusieurs contrats à terme.
Avec f1, f2, f3
trois Future[String]
qui contiendront les chaînes one, two, three
respectivement,
val fCombined =
for {
s1 <- f1
s2 <- f2
s3 <- f3
} yield (s"$s1 - $s2 - $s3")
fCombined
sera un Future[String]
contenant la chaîne one - two - three
une fois que tous les futurs auront été exécutés avec succès.
Notez qu'un ExectionContext implicite est supposé ici.
De plus, gardez à l'esprit que la compréhension n'est qu'un sucre syntaxique pour une méthode flatMap, de sorte que la construction d'objets futurs pour body éliminerait l'exécution simultanée de blocs de code entourés de futurs et aboutirait à un code séquentiel. Vous le voyez sur l'exemple:
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
La valeur entourée par l'objet result1
serait toujours négative alors que result2
serait positif.
Pour plus de détails sur la compréhension et le yield
en général, voir http://docs.scala-lang.org/tutorials/FAQ/yield.html