Scala Language
Futures
Sök…
Skapa en framtid
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
}
}
Helt enkelt den divide
skapar metoden en framtid som kommer att lösa med kvoten av a
över b
.
Konsumerar en framgångsrik framtid
Det enklaste sättet att konsumera en framgångsrik Future-- eller snarare få värdet inuti Future-- är att använda map
metoden. Antag lite kod anropar divide
metoden enligt FutureDivider
objektet från "Skapa en framtid" exempel. Hur skulle koden behöva se ut för att få kvoten på a
över b
?
object Calculator {
def calculateAndReport(a: Int, b: Int) = {
val eventualQuotient = FutureDivider divide(a, b)
eventualQuotient map {
quotient => println(quotient)
}
}
}
Konsumerar en misslyckad framtid
Ibland kan beräkningen i en framtid skapa ett undantag, vilket gör att framtiden misslyckas. I exemplet "Skapa en framtid", vad händer om anropskoden passerade 55
och 0
till divide
? Det skulle kasta en ArithmeticException
efter att ha försökt dela med noll, naturligtvis. Hur skulle det hanteras i konsumtionskod? Det finns faktiskt en handfull sätt att hantera misslyckanden.
Hantera undantaget med recover
och mönstermatchning.
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}")
}
}
}
Hantera undantaget med den failed
projektionen, där undantaget blir värdet för framtiden:
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}")
}
}
}
Sätta framtiden tillsammans
De tidigare exemplen visade de enskilda funktionerna i en framtid, hanterar framgångar och misslyckade fall. Vanligtvis hanteras emellertid båda funktionerna mycket mer utsträckt. Här är exemplet, skrivet på ett snyggare och mer realistiskt sätt:
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}")
}
}
}
Sekvensering och korsning av Futures
I vissa fall är det nödvändigt att beräkna en variabel mängd värden på separata Futures. Antag att du har en List[Future[Int]]
, men istället måste en List[Int]
bearbetas. Då är frågan hur man förvandlar den här instansen av List[Future[Int]]
till en Future[List[Int]]
. För detta ändamål finns det sequence
på Future
companion-objektet.
def listOfFuture: List[Future[Int]] = List(1,2,3).map(Future(_))
def futureOfList: Future[List[Int]] = Future.sequence(listOfFuture)
I allmänhet är sequence
en vanligt känd operatör inom världen av funktionell programmering som omvandlar F[G[T]]
till G[F[T]]
med begränsningar till F
och G
Det finns en alternativ operatör som heter traverse
, som fungerar på liknande sätt men tar en funktion som ett extra argument. Med identitetsfunktionen x => x
som en parameter uppträder den som sequence
.
def listOfFuture: List[Future[Int]] = List(1,2,3).map(Future(_))
def futureOfList: Future[List[Int]] = Future.traverse(listOfFuture)(x => x)
Det extra argumentet tillåter dock att modifiera varje framtida instans inom den givna listOfFuture
. Dessutom behöver det första argumentet inte vara en lista över Future
. Därför är det möjligt att omvandla exemplet enligt följande:
def futureOfList: Future[List[Int]] = Future.traverse(List(1,2,3))(Future(_))
I det här fallet överförs List(1,2,3)
direkt som första argument och identitetsfunktionen x => x
ersätts med funktionen Future(_)
att på liknande sätt linda in varje Int
värde till en Future
. En fördel med detta är att mellanhandslistan List[Future[Int]]
kan utelämnas för att förbättra prestandan.
Kombinera flera framtider - för förståelse
Förståelsen är ett kompakt sätt att köra ett kodblock som beror på det framgångsrika resultatet av flera framtider.
Med f1, f2, f3
tre Future[String]
: er som kommer att innehålla strängarna respektive, one, two, three
,
val fCombined =
for {
s1 <- f1
s2 <- f2
s3 <- f3
} yield (s"$s1 - $s2 - $s3")
fCombined
kommer att vara en Future[String]
innehåller strängen one - two - three
när alla futures har avslutats.
Observera att en implicit ExectionContext antas här.
Tänk också på att för förståelse bara är ett syntaktiskt socker för en plattMapp-metod, så framtida föremålens konstruktion inuti för kroppen skulle eliminera samtidigt exekvering av kodblock som är inneslutna av futures och leder till sekventiell kod. Du ser det på exempel:
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
Värde omslutet av result1
objektet skulle alltid vara negativt medan result2
skulle vara positivt.
För mer information om förståelse och yield
i allmänhet, se http://docs.scala-lang.org/tutorials/FAQ/yield.html