Scala Language
futures
Zoeken…
Een toekomst creëren
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
}
}
Heel eenvoudig, de divide
creëert een toekomst die zal oplossen met het quotiënt van a
over b
.
Een succesvolle toekomst gebruiken
De eenvoudigste manier om een succesvolle Future-- consumeren of beter gezegd, krijgen de waarde in de Future-- is om het te gebruiken map
methode. Stel dat sommige code roept de divide
werkwijze van de FutureDivider
object uit het "creëren van een toekomst" voorbeeld. Hoe zou de code eruit moeten zien om het quotiënt van a
over b
?
object Calculator {
def calculateAndReport(a: Int, b: Int) = {
val eventualQuotient = FutureDivider divide(a, b)
eventualQuotient map {
quotient => println(quotient)
}
}
}
Een mislukte toekomst gebruiken
Soms kan de berekening in een toekomst een uitzondering maken, waardoor de toekomst mislukt. Wat als in het voorbeeld "Een toekomst creëren" de belcode 55
en 0
divide
aan de divide
? Het zou natuurlijk een ArithmeticException
na proberen te delen door nul, natuurlijk. Hoe zou dat worden verwerkt in het consumeren van code? Er zijn eigenlijk een handvol manieren om met storingen om te gaan.
Behandel de uitzondering met recover
en patroonovereenkomst.
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}")
}
}
}
Behandel de uitzondering met de failed
projectie, waarbij de uitzondering de waarde van de toekomst wordt:
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}")
}
}
}
De toekomst samenbrengen
De vorige voorbeelden demonstreerden de individuele kenmerken van een toekomst, het afhandelen van succes- en faalgevallen. Meestal worden beide functies echter veel strenger behandeld. Hier is het voorbeeld, geschreven op een nettere en meer realistische manier:
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}")
}
}
}
Reeksen en doorlopen Futures
In sommige gevallen is het noodzakelijk om een variabel aantal waarden op afzonderlijke Futures te berekenen. Stel dat u een List[Future[Int]]
, maar in plaats daarvan moet een List[Int]
worden verwerkt. De vraag is dan hoe dit exemplaar van List[Future[Int]]
in een Future[List[Int]]
omgezet. Voor dit doel is er de sequence
op het Future
bijbehorende object.
def listOfFuture: List[Future[Int]] = List(1,2,3).map(Future(_))
def futureOfList: Future[List[Int]] = Future.sequence(listOfFuture)
In het algemeen is de sequence
een algemeen bekende operator in de wereld van functioneel programmeren die F[G[T]]
omzet in G[F[T]]
met beperkingen voor F
en G
Er is een alternatieve operator genaamd traverse
, die vergelijkbaar werkt, maar een functie als extra argument neemt. Met de identiteitsfunctie x => x
als parameter gedraagt deze zich als de sequence
.
def listOfFuture: List[Future[Int]] = List(1,2,3).map(Future(_))
def futureOfList: Future[List[Int]] = Future.traverse(listOfFuture)(x => x)
Het extra argument maakt het echter mogelijk om elke toekomstige instantie binnen de gegeven listOfFuture
te wijzigen. Bovendien hoeft het eerste argument geen lijst van Future
. Daarom is het mogelijk om het voorbeeld als volgt te transformeren:
def futureOfList: Future[List[Int]] = Future.traverse(List(1,2,3))(Future(_))
In dit geval wordt de List(1,2,3)
direct doorgegeven als eerste argument en wordt de identiteitsfunctie x => x
vervangen door de functie Future(_)
om op dezelfde manier elke Int
waarde om te zetten in een Future
. Een voordeel hiervan is dat de tussenliggende List[Future[Int]]
kan worden weggelaten om de prestaties te verbeteren.
Combineer meerdere toekomsten - voor een beter begrip
Het begrip is een compacte manier om een codeblok uit te voeren dat afhankelijk is van het succesvolle resultaat van meerdere futures.
Met f1, f2, f3
drie Future[String]
's die respectievelijk de tekenreeksen one, two, three
,
val fCombined =
for {
s1 <- f1
s2 <- f2
s3 <- f3
} yield (s"$s1 - $s2 - $s3")
fCombined
wordt een Future[String]
met de string one - two - three
zodra alle futures met succes zijn voltooid.
Merk op dat hier een impliciete ExectionContext wordt verondersteld.
Houd er ook rekening mee dat voor begrip slechts een syntactische suiker is voor een flatMap-methode, zodat toekomstige objectenconstructie voor het lichaam de gelijktijdige uitvoering van codeblokken omsloten door futures zou elimineren en zou leiden tot opeenvolgende code. Je ziet het bijvoorbeeld:
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
Waarde ingesloten door result1
object zou altijd negatief zijn, terwijl result2
positief zou zijn.
Zie http://docs.scala-lang.org/tutorials/FAQ/yield.html voor meer informatie over begrip en yield
in het algemeen.