CompletableFuture | thenApply vs thenCompose

119

Nie mogę zrozumieć różnicy między thenApply() a thenCompose().

Czy więc ktoś mógłby podać prawidłowy przypadek użycia?

Z dokumentacji Java:

thenApply(Function<? super T,? extends U> fn)

Zwraca nową wartość, CompletionStagektóra po normalnym zakończeniu tego etapu jest wykonywana z wynikiem tego etapu jako argumentem dostarczonej funkcji.

thenCompose(Function<? super T,? extends CompletionStage<U>> fn)

Zwraca nową wartość, CompletionStagektóra po normalnym zakończeniu tego etapu jest wykonywana z tym etapem jako argumentem dostarczonej funkcji.

Rozumiem, że drugi argument thenComposerozszerza CompletionStage, gdzie thenApplynie.

Czy ktoś mógłby podać przykład, w którym przypadku muszę użyć thenApplyi kiedy thenCompose?

GuyT
źródło
39
Czy rozumiesz różnicę między mapi flatMapw Stream? thenApplyjest mapi thenComposejest flatMapz CompletableFuture. Używasz, thenComposeaby uniknąć posiadania CompletableFuture<CompletableFuture<..>>.
Misha
2
@ Misha Dzięki za komentarz. Tak, znam różnicę między mapi flatMapi rozumiem twój punkt widzenia.
Jeszcze
To bardzo fajny przewodnik na początek CompletableFuture - baeldung.com/java-completablefuture
thealchemist

Odpowiedzi:

168

thenApply jest używany, jeśli masz synchroniczną funkcję mapowania.

CompletableFuture<Integer> future = 
    CompletableFuture.supplyAsync(() -> 1)
                     .thenApply(x -> x+1);

thenComposejest używany, jeśli masz asynchroniczną funkcję mapującą (tj. taką, która zwraca a CompletableFuture). Następnie zwróci przyszłość bezpośrednio z wynikiem, a nie zagnieżdżoną przyszłość.

CompletableFuture<Integer> future = 
    CompletableFuture.supplyAsync(() -> 1)
                     .thenCompose(x -> CompletableFuture.supplyAsync(() -> x+1));
Joe C
źródło
14
Dlaczego programista powinien używać .thenCompose(x -> CompletableFuture.supplyAsync(() -> x+1))zamiast .thenApplyAsync(x -> x+1)? Bycie synchronicznym lub asynchronicznym nie jest istotną różnicą.
Holger
17
Nie zrobiliby tego w ten sposób. Jeśli jednak biblioteka innej firmy, której użyli, zwróciłaby wartość CompletableFuture, to byłby thenComposeto sposób na spłaszczenie struktury.
Joe C
1
@ArunavSanyal, głosy pokazują inny obraz. Ta odpowiedź jest jasna i zwięzła.
Alex Shesterov
@Holger przeczytaj moją drugą odpowiedź, jeśli jesteś zdezorientowany, thenApplyAsyncponieważ nie jest to, o czym myślisz.
1283822
@ 1283822 Nie wiem, co sprawia, że ​​myślisz, że byłem zdezorientowany i nie ma nic w Twojej odpowiedzi, która potwierdza Twoje twierdzenie, że „to nie jest to, o czym myślisz”.
Holger,
49

Myślę, że odpowiedź zamieszczona przez @Joe C jest myląca.

Spróbuję wyjaśnić różnicę między thenApplyi thenComposena przykładzie.

Załóżmy, że mamy 2 metody: getUserInfo(int userId)i getUserRating(UserInfo userInfo):

public CompletableFuture<UserInfo> getUserInfo(userId)

public CompletableFuture<UserRating> getUserRating(UserInfo)

Oba typy zwracanych metod to CompletableFuture.

Chcemy getUserInfo()najpierw wywołać , a po jego zakończeniu zadzwonić getUserRating()z wynikiem UserInfo.

Po zakończeniu getUserInfo()metody wypróbujmy oba thenApplyi thenCompose. Różnica polega na typach zwracanych:

CompletableFuture<CompletableFuture<UserRating>> f =
    userInfo.thenApply(this::getUserRating);

CompletableFuture<UserRating> relevanceFuture =
    userInfo.thenCompose(this::getUserRating);

thenCompose()działa jak Scala,flatMap która spłaszcza zagnieżdżone futures.

thenApply()zwrócił zagnieżdżone futures bez zmian, ale thenCompose()spłaszczył zagnieżdżone, CompletableFuturesaby łatwiej było połączyć z nim więcej wywołań metod.

Dordże
źródło
2
Zatem odpowiedź Joe C nie jest myląca. Jest poprawne i bardziej zwięzłe.
koleS
2
To było bardzo pomocne :-)
dstibbe
1
Imho to kiepski projekt, aby napisać CompletableFuture <UserInfo> getUserInfo i CompletableFuture <UserRating> getUserRating (UserInfo) \\ zamiast tego powinno to być UserInfo getUserInfo () i int getUserRating (UserInfo), jeśli chcę go używać asynchronicznie i łańcuchowo, mogę użyj ompletableFuture.supplyAsync (x => getUserInfo (userId)). thenApply (userInfo => getUserRating (userInfo)) lub czegoś podobnego, jest to bardziej czytelne imho i nie jest obowiązkowe zawijanie WSZYSTKICH zwracanych typów do CompletableFuture
user1694306
@ user1694306 To, czy jest to zły projekt, czy nie, zależy od tego, czy ocena użytkownika jest zawarta w UserInfo(wtedy tak), czy też musi być uzyskana oddzielnie, może nawet kosztowna (wtedy nie).
glglgl
42

Zaktualizowany Javadocs w Javie 9 prawdopodobnie pomoże lepiej to zrozumieć:

następnie zastosuj

<U> CompletionStage<U> thenApply​(Function<? super T,? extends U> fn)

Zwraca nową wartość, CompletionStagektóra po normalnym zakończeniu tego etapu jest wykonywana z wynikiem tego etapu jako argumentem dostarczonej funkcji.

Ta metoda jest analogiczna do Optional.mapi Stream.map.

Zobacz CompletionStagedokumentację, aby poznać zasady dotyczące wyjątkowego ukończenia.

następnie utwórz

<U> CompletionStage<U> thenCompose​(Function<? super T,? extends CompletionStage<U>> fn)

Zwraca nowy, CompletionStagektóry jest zakończony tą samą wartością, co CompletionStagezwrócona przez daną funkcję.

Gdy ten etap zakończy się normalnie, dana funkcja jest wywoływana z wynikiem tego etapu jako argumentem, zwracając inną CompletionStage. Gdy ten etap kończy się normalnie, CompletionStagezwracany przez tę metodę jest zakończony tą samą wartością.

Aby zapewnić postęp, dostarczona funkcja musi zaaranżować ostateczne zakończenie jej wyniku.

Ta metoda jest analogiczna do Optional.flatMapi Stream.flatMap.

Zobacz CompletionStagedokumentację, aby poznać zasady dotyczące wyjątkowego ukończenia.

Didier L.
źródło
14
Zastanawiam się, dlaczego nie nazwali tych funkcji mapi flatMapprzede wszystkim.
Matthias Braun
1
@MatthiasBraun Myślę, że to dlatego, thenApply()że po prostu zadzwoni Function.apply()i thenCompose()jest trochę podobny do tworzenia funkcji.
Didier L
14

thenApplyi thenComposesą metodami CompletableFuture. Używaj ich, jeśli zamierzasz zrobić coś, aby uzyskać CompleteableFuturewynik za pomocą Function.

thenApplyi thenComposeoba zwracają CompletableFuturejako własny wynik. Możesz łączyć wiele thenApplylub thenComposerazem. Podaj a Functiondo każdego wywołania, którego wynik będzie wejściem do następnego Function.

To, Functionco dostarczyłeś, czasami musi zrobić coś synchronicznie. Zwracany typ nie Functionpowinien być Futuretypem. W takim przypadku powinieneś użyć thenApply.

CompletableFuture.completedFuture(1)
    .thenApply((x)->x+1) // adding one to the result synchronously, returns int
    .thenApply((y)->System.println(y)); // value of y is 1 + 1 = 2

Innym razem możesz chcieć wykonać asynchroniczne przetwarzanie w tym Function. W takim przypadku powinieneś użyć thenCompose. Typ zwrotu Functionpowinien być a CompletionStage. Następny Functionw łańcuchu otrzyma wynik tego CompletionStagejako dane wejściowe, tym samym rozpakowując plik CompletionStage.

// addOneAsync may be implemented by using another thread, or calling a remote method
abstract CompletableFuture<Integer> addOneAsync(int input);

CompletableFuture.completedFuture(1)
    .thenCompose((x)->addOneAsync(x)) // doing something asynchronous, returns CompletableFuture<Integer>
    .thenApply((y)->System.println(y)); // y is an Integer, the result of CompletableFuture<Integer> above

To jest podobny pomysł do Javascript Promise. Promise.thenmoże akceptować funkcję, która zwraca wartość lub a Promisez wartości. Powodem, dla którego te dwie metody mają różne nazwy w Javie, jest ogólne wymazywanie . Function<? super T,? extends U> fni Function<? super T,? extends CompletionStage<U>> fnsą uważane za ten sam typ środowiska wykonawczego - Function. Dlatego thenApplyi thenComposemusi być wyraźnie nazwany, inaczej kompilator Java narzekałby na identyczne sygnatury metod. Efektem końcowym jest to, że JavaScript Promise.thenjest zaimplementowany w dwóch częściach - thenApplyi thenCompose- w Javie.

Możesz przeczytać moją drugą odpowiedź, jeśli nie masz pewności co do powiązanej funkcji thenApplyAsync.

1283822
źródło