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



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow