Scala Language
Futures
Szukaj…
Tworzenie przyszłości
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
}
}
Dość prosto, divide
metoda tworzy przyszłość, która rozwiąże z ilorazu a
nad b
.
Odnosząc pomyślną przyszłość
Najłatwiejszym sposobem na zdobycie udanej przyszłości - a raczej uzyskanie wartości w przyszłości - jest użycie metody map
. Przypuśćmy, że jakiś kod wywołuje divide
sposobu FutureDivider
obiektu z „Tworzenie przyszłości” przykład. Co by potrzeba kod wyglądać, aby uzyskać iloraz a
nad b
?
object Calculator {
def calculateAndReport(a: Int, b: Int) = {
val eventualQuotient = FutureDivider divide(a, b)
eventualQuotient map {
quotient => println(quotient)
}
}
}
Spożywanie nieudanej przyszłości
Czasami obliczenia w przyszłości mogą stworzyć wyjątek, który spowoduje, że przyszłość zawiedzie. Co w przykładzie „Tworzenie przyszłości”, jeśli kod wywołujący przekazał 55
i 0
do metody divide
? Zgłasza wyjątek ArithmeticException
po próbie podzielenia przez zero, oczywiście. Jak poradziłby sobie z tym kod konsumujący? Istnieje naprawdę kilka sposobów radzenia sobie z awariami.
Obsługa wyjątku z recover
i dopasowywaniem wzorców.
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}")
}
}
}
Obsługa wyjątku przy failed
projekcji, gdzie wyjątek staje się wartością Przyszłości:
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}")
}
}
}
Łącząc przyszłość
Poprzednie przykłady pokazały indywidualne cechy przyszłości, radzenie sobie z przypadkami sukcesów i niepowodzeń. Zwykle jednak obie funkcje są obsługiwane o wiele bardziej zwięźle. Oto przykład, napisany w zgrabniejszy i bardziej realistyczny sposób:
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}")
}
}
}
Sekwencjonowanie i przemierzanie kontraktów futures
W niektórych przypadkach konieczne jest obliczenie zmiennej ilości wartości dla poszczególnych kontraktów futures. Załóżmy, że masz List[Future[Int]]
, ale zamiast tego List[Int]
wymaga przetworzenia. Zatem pytanie brzmi: jak zamienić to wystąpienie List[Future[Int]]
w Future[List[Int]]
. W tym celu istnieje metoda sequence
na obiekcie towarzyszącym Future
.
def listOfFuture: List[Future[Int]] = List(1,2,3).map(Future(_))
def futureOfList: Future[List[Int]] = Future.sequence(listOfFuture)
Ogólnie sequence
jest powszechnie znanym operatorem w świecie programowania funkcjonalnego, który przekształca F[G[T]]
w G[F[T]]
z ograniczeniami do F
i G
Istnieje alternatywny operator o nazwie traverse
, który działa podobnie, ale przyjmuje funkcję jako dodatkowy argument. Dzięki funkcji tożsamości x => x
jako parametrowi zachowuje się jak operator sequence
.
def listOfFuture: List[Future[Int]] = List(1,2,3).map(Future(_))
def futureOfList: Future[List[Int]] = Future.traverse(listOfFuture)(x => x)
Jednak dodatkowy argument pozwala modyfikować każdą przyszłą instancję wewnątrz podanego listOfFuture
. Ponadto pierwszym argumentem nie musi być lista Future
. Dlatego możliwe jest przekształcenie przykładu w następujący sposób:
def futureOfList: Future[List[Int]] = Future.traverse(List(1,2,3))(Future(_))
W tym przypadku List(1,2,3)
jest przekazywany bezpośrednio jako pierwszy argument, a funkcja tożsamości x => x
jest zastępowana funkcją Future(_)
aby podobnie zawijać każdą wartość Int
do Future
. Zaletą tego jest to, że można pominąć List[Future[Int]]
pośrednią List[Future[Int]]
aby poprawić wydajność.
Łącz wiele kontraktów futures - dla zrozumienia
Zrozumienie to kompaktowy sposób na uruchomienie bloku kodu, który zależy od pomyślnego wyniku wielu przyszłości.
W przypadku f1, f2, f3
trzy Future[String]
ciągi Future[String]
, które będą zawierać odpowiednio ciągi one, two, three
,
val fCombined =
for {
s1 <- f1
s2 <- f2
s3 <- f3
} yield (s"$s1 - $s2 - $s3")
fCombined
będzie Future[String]
zawierającym ciąg one - two - three
po pomyślnym zakończeniu wszystkich kontraktów terminowych.
Zauważ, że zakłada się tutaj domyślny kontekst ExectionContext.
Pamiętaj również, że dla zrozumienia jest to tylko cukier syntaktyczny dla metody flatMap, więc konstrukcja przyszłych obiektów wewnątrz ciała wyeliminowałaby równoczesne wykonywanie bloków kodu otoczonych futures i prowadzi do kodu sekwencyjnego. Widzisz to na przykładzie:
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
Wartość result1
obiekcie result1
będzie zawsze ujemna, a result2
będzie dodatni.
Aby uzyskać więcej informacji na temat zrozumienia i yield
w ogóle, zobacz http://docs.scala-lang.org/tutorials/FAQ/yield.html