CompletableFuture | thenApply vs thenComposer

119

Je n'arrive pas à comprendre la différence entre thenApply() et thenCompose().

Alors, quelqu'un pourrait-il fournir un cas d'utilisation valide?

À partir de la documentation Java:

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

Renvoie un nouveau CompletionStagequi, lorsque cette étape se termine normalement, est exécuté avec le résultat de cette étape comme argument de la fonction fournie.

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

Renvoie un nouveau CompletionStagequi, lorsque cette étape se termine normalement, est exécuté avec cette étape comme argument de la fonction fournie.

J'obtiens que le 2ème argument d' thenComposeétend le CompletionStage là où thenApplyne le fait pas.

Quelqu'un pourrait-il donner un exemple dans quel cas je dois utiliser thenApplyet quand thenCompose?

GuyT
la source
39
Comprenez-vous la différence entre mapet flatMapdans Stream? thenApplyest le mapet thenComposeest le flatMapde CompletableFuture. Vous utilisez thenComposepour éviter d'avoir CompletableFuture<CompletableFuture<..>>.
Misha
2
@Misha Merci pour votre commentaire. Oui, je connais la différence entre mapet flatMapet je comprends votre point. Merci encore :)
GuyT
Ceci est un très bon guide pour commencer avec CompletableFuture - baeldung.com/java-completablefuture
thealchemist

Réponses:

168

thenApply est utilisé si vous avez une fonction de mappage synchrone.

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

thenComposeest utilisé si vous avez une fonction de mappage asynchrone (c'est-à-dire une fonction qui renvoie a CompletableFuture). Il renverra alors un futur avec le résultat directement, plutôt qu'un futur imbriqué.

CompletableFuture<Integer> future = 
    CompletableFuture.supplyAsync(() -> 1)
                     .thenCompose(x -> CompletableFuture.supplyAsync(() -> x+1));
Joe C
la source
14
Pourquoi un programmeur devrait-il utiliser .thenCompose(x -> CompletableFuture.supplyAsync(() -> x+1))au lieu de .thenApplyAsync(x -> x+1)? Être synchrone ou asynchrone n'est pas la différence pertinente.
Holger
17
Ils ne le feraient pas comme ça. Cependant, si une bibliothèque tierce qu'ils utilisaient retournait un CompletableFuture, alors ce serait là où thenComposeserait le moyen d'aplatir la structure.
Joe C
1
@ArunavSanyal, les votes montrent une image différente. Cette réponse est claire et concise.
Alex Shesterov
@Holger lisez mon autre réponse si vous êtes confus thenApplyAsyncparce que ce n'est pas ce que vous pensez que c'est.
1283822
@ 1283822 Je ne sais pas ce qui vous fait penser que j'étais confus et il n'y a rien dans votre réponse pour étayer votre affirmation que "ce n'est pas ce que vous pensez que c'est".
Holger
49

Je pense que la réponse publiée par @Joe C est trompeuse.

Laissez-moi essayer d'expliquer la différence entre thenApplyet thenComposeavec un exemple.

Supposons que nous ayons 2 méthodes: getUserInfo(int userId)et getUserRating(UserInfo userInfo):

public CompletableFuture<UserInfo> getUserInfo(userId)

public CompletableFuture<UserRating> getUserRating(UserInfo)

Les deux types de retour de méthode sont CompletableFuture.

Nous voulons appeler en getUserInfo()premier, et une fois terminé, appeler getUserRating()avec le résultat UserInfo.

À la fin de la getUserInfo()méthode, essayons les deux thenApplyet thenCompose. La différence réside dans les types de retour:

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

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

thenCompose()fonctionne comme ScalaflatMap qui aplatit les futurs imbriqués.

thenApply()a renvoyé les futurs imbriqués tels qu'ils étaient, mais a thenCompose()aplati les futurs imbriqués CompletableFuturesafin qu'il soit plus facile de chaîner plus d'appels de méthode.

Dorjee
la source
2
La réponse de Joe C n'est donc pas trompeuse. C'est correct et plus concis.
koleS
2
Cela a été très utile :-)
dstibbe
1
À mon avis, il est mal conçu d'écrire CompletableFuture <UserInfo> getUserInfo et CompletableFuture <UserRating> getUserRating (UserInfo) \\ à la place, cela devrait être UserInfo getUserInfo () et int getUserRating (UserInfo) si je veux l'utiliser async et chain, alors je peux l'utiliser utilisez ompletableFuture.supplyAsync (x => getUserInfo (userId)). thenApply (userInfo => getUserRating (userInfo)) ou quelque chose comme ça, c'est plus lisible à mon humble avis, et pas obligatoire pour envelopper TOUS les types de retour dans CompletableFuture
user1694306
@ user1694306 Que la conception soit médiocre ou non dépend du fait que l'évaluation de l'utilisateur est contenue dans le UserInfo(alors oui) ou si elle doit être obtenue séparément, peut-être même coûteuse (puis non).
glglgl le
42

Les Javadocs mis à jour dans Java 9 aideront probablement à mieux le comprendre:

puis appliquer

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

Renvoie un nouveau CompletionStagequi, lorsque cette étape se termine normalement, est exécuté avec le résultat de cette étape comme argument de la fonction fournie.

Cette méthode est analogue à Optional.mapet Stream.map.

Consultez la CompletionStagedocumentation pour connaître les règles relatives à la réalisation exceptionnelle.

puisComposer

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

Renvoie un nouveau CompletionStagequi est complété avec la même valeur que celui CompletionStagerenvoyé par la fonction donnée.

Lorsque cette étape se termine normalement, la fonction donnée est appelée avec le résultat de cette étape comme argument, en renvoyant une autre CompletionStage. Lorsque cette étape se termine normalement, le CompletionStagerenvoyé par cette méthode est terminé avec la même valeur.

Pour assurer la progression, la fonction fournie doit organiser la réalisation éventuelle de son résultat.

Cette méthode est analogue à Optional.flatMapet Stream.flatMap.

Consultez la CompletionStagedocumentation pour connaître les règles relatives à la réalisation exceptionnelle.

Didier L
la source
14
Je me demande pourquoi ils n'ont pas nommé ces fonctions mapet flatMapen premier lieu.
Matthias Braun
1
@MatthiasBraun Je pense que c'est parce que je thenApply()vais simplement appeler Function.apply(), et thenCompose()c'est un peu similaire à la composition de fonctions.
Didier L
14

thenApplyet thenComposesont des méthodes de CompletableFuture. Utilisez-les lorsque vous avez l'intention de faire quelque chose pour CompleteableFuturele résultat avec a Function.

thenApplyet les thenComposedeux renvoient a CompletableFuturecomme leur propre résultat. Vous pouvez enchaîner plusieurs thenApplyou thenComposeensemble. Fournissez un Functionà chaque appel, dont le résultat sera l'entrée du suivant Function.

Le que Functionvous avez fourni doit parfois faire quelque chose de manière synchrone. Le type de retour de votre Functiondoit être un non- Futuretype. Dans ce cas, vous devez utiliser 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

D'autres fois, vous souhaiterez peut-être effectuer un traitement asynchrone Function. Dans ce cas, vous devez utiliser thenCompose. Le type de retour de votre Functiondevrait être un CompletionStage. Le suivant Functiondans la chaîne obtiendra le résultat de cela CompletionStagecomme entrée, déballant ainsi le fichier 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

C'est une idée similaire à celle de Javascript Promise. Promise.thenpeut accepter une fonction qui renvoie une valeur ou Promiseune valeur. La raison pour laquelle ces deux méthodes ont des noms différents en Java est due à l' effacement générique . Function<? super T,? extends U> fnet Function<? super T,? extends CompletionStage<U>> fnsont considérés comme du même type d'exécution - Function. Ainsi thenApplyet thenComposedoivent être nommés distinctement, sinon le compilateur Java se plaindrait de signatures de méthode identiques. Le résultat final étant, Javascript Promise.thenest implémenté en deux parties - thenApplyet thenCompose- en Java.

Vous pouvez lire mon autre réponse si vous êtes également confus au sujet d'une fonction connexe thenApplyAsync.

1283822
la source