Scala Language
Futures
Ricerca…
Creare 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
}
}
Molto semplicemente, il metodo di divide
crea un futuro che si risolverà con il quoziente di a
b
.
Consumare un futuro di successo
Il modo più semplice per consumare un futuro di successo - o meglio, ottenere il valore all'interno del futuro - è utilizzare il metodo della map
. Supponiamo che alcuni codici chiamino il metodo di divide
dell'oggetto FutureDivider
dall'esempio "Creating a Future". Come dovrebbe apparire il codice per ottenere il quoziente di a
b
?
object Calculator {
def calculateAndReport(a: Int, b: Int) = {
val eventualQuotient = FutureDivider divide(a, b)
eventualQuotient map {
quotient => println(quotient)
}
}
}
Consumare un futuro fallito
A volte il calcolo in un futuro può creare un'eccezione, che farà fallire il futuro. Nell'esempio "Creating a Future", cosa accadrebbe se il codice chiamante passasse 55
e 0
al metodo divide
? Avrebbe gettato un ArithmeticException
dopo aver provato a dividere per zero, ovviamente. Come sarebbe gestito nel codice che consuma? Ci sono in realtà una manciata di modi per affrontare i fallimenti.
Gestire l'eccezione con il recover
e la corrispondenza del modello.
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}")
}
}
}
Gestire l'eccezione con il failed
proiezione, dove l'eccezione diventa il valore 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}")
}
}
}
Mettere insieme il futuro
Gli esempi precedenti hanno dimostrato le caratteristiche individuali di un futuro, gestendo casi di successo e fallimento. Di solito, tuttavia, entrambe le funzionalità vengono gestite in modo molto più preciso. Ecco l'esempio, scritto in modo più ordinato e più realistico:
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}")
}
}
}
Sequencing and traversing Futures
In alcuni casi è necessario calcolare una quantità variabile di valori su Futures separati. Si supponga di avere una List[Future[Int]]
, ma invece una List[Int]
deve essere elaborata. Quindi la domanda è come trasformare questa istanza di List[Future[Int]]
in un Future[List[Int]]
. Per questo scopo c'è il metodo di sequence
sull'oggetto compagno Future
.
def listOfFuture: List[Future[Int]] = List(1,2,3).map(Future(_))
def futureOfList: Future[List[Int]] = Future.sequence(listOfFuture)
In generale, la sequence
è un operatore comunemente noto nel mondo della programmazione funzionale che trasforma F[G[T]]
in G[F[T]]
con restrizioni a F
e G
Esiste un operatore alternativo chiamato traverse
, che funziona in modo simile ma prende una funzione come argomento aggiuntivo. Con la funzione di identità x => x
come parametro si comporta come l'operatore di sequence
.
def listOfFuture: List[Future[Int]] = List(1,2,3).map(Future(_))
def futureOfList: Future[List[Int]] = Future.traverse(listOfFuture)(x => x)
Tuttavia, l'argomento extra consente di modificare ogni istanza futura all'interno della listOfFuture
. Inoltre, il primo argomento non deve essere una lista di Future
. Pertanto è possibile trasformare l'esempio come segue:
def futureOfList: Future[List[Int]] = Future.traverse(List(1,2,3))(Future(_))
In questo caso l' List(1,2,3)
viene passato direttamente come primo argomento e la funzione di identità x => x
viene sostituita con la funzione Future(_)
per avvolgere in modo simile ciascun valore Int
in un Future
. Un vantaggio di questo è che l' List[Future[Int]]
intermedio List[Future[Int]]
può essere omesso per migliorare le prestazioni.
Combina più Futures - Per Comprensione
La comprensione è un modo compatto per eseguire un blocco di codice che dipende dal risultato positivo di più futuri.
Con f1, f2, f3
tre Future[String]
che conterranno le stringhe one, two, three
rispettivamente,
val fCombined =
for {
s1 <- f1
s2 <- f2
s3 <- f3
} yield (s"$s1 - $s2 - $s3")
fCombined
sarà una Future[String]
contenente la stringa one - two - three
una volta che tutti i futures sono stati completati con successo.
Si noti che qui viene assunto un implicito ExectionContext.
Inoltre, tieni a mente che per la comprensione è solo uno zucchero sintattico per un metodo flatMap, quindi la costruzione di oggetti futuri all'interno del corpo eliminerebbe l'esecuzione simultanea di blocchi di codice racchiusi dai futures e condurrebbe al codice sequenziale. Lo vedi nell'esempio:
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
Il valore racchiuso dall'oggetto result1
sarebbe sempre negativo mentre il result2
sarebbe positivo.
Per maggiori dettagli sulla comprensione e sulla yield
in generale, vedi http://docs.scala-lang.org/tutorials/FAQ/yield.html