CompletableFuture | thenApply vs thenCompose

119

No puedo entender la diferencia entre thenApply() y thenCompose().

Entonces, ¿alguien podría proporcionar un caso de uso válido?

De los documentos de Java:

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

Devuelve un nuevo CompletionStageque, cuando esta etapa se completa normalmente, se ejecuta con el resultado de esta etapa como argumento de la función proporcionada.

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

Devuelve un nuevo CompletionStageque, cuando esta etapa se completa normalmente, se ejecuta con esta etapa como argumento para la función proporcionada.

Entiendo que el segundo argumento de thenComposeCompletionStage se extiende donde thenApplyno lo hace.

¿Alguien podría dar un ejemplo en qué caso debo usar thenApplyy cuándo thenCompose?

GuyT
fuente
39
¿Entiendes la diferencia entre mapy flatMapen Stream? thenApplyes el mapy thenComposees el flatMapde CompletableFuture. Usas thenComposepara evitar tener CompletableFuture<CompletableFuture<..>>.
Misha
2
@Misha Gracias por tu comentario. Sí, conozco la diferencia entre mapy flatMapy entiendo tu punto. Gracias de nuevo :)
GuyT
Esta es una guía muy buena para comenzar con CompletableFuture - baeldung.com/java-completablefuture
thealchemist

Respuestas:

168

thenApply se utiliza si tiene una función de mapeo síncrono.

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

thenComposese utiliza si tiene una función de mapeo asincrónica (es decir, una que devuelve a CompletableFuture). Luego devolverá un futuro con el resultado directamente, en lugar de un futuro anidado.

CompletableFuture<Integer> future = 
    CompletableFuture.supplyAsync(() -> 1)
                     .thenCompose(x -> CompletableFuture.supplyAsync(() -> x+1));
Joe C
fuente
14
¿Por qué debería usar un programador en .thenCompose(x -> CompletableFuture.supplyAsync(() -> x+1))lugar de .thenApplyAsync(x -> x+1)? Ser sincrónico o asincrónico no es la diferencia relevante.
Holger
17
No lo harían así. Sin embargo, si una biblioteca de terceros que usaron devolvió un CompletableFuture, entonces esta sería thenComposela forma de aplanar la estructura.
Joe C
1
@ArunavSanyal, los votos muestran una imagen diferente. Esta respuesta es clara y concisa.
Alex Shesterov
@Holger lea mi otra respuesta si está confundido thenApplyAsyncporque no es lo que cree que es.
1283822
@ 1283822 No sé qué te hace pensar que estaba confundido y no hay nada en tu respuesta que respalde tu afirmación de que “no es lo que crees que es”.
Holger
49

Creo que la respuesta publicada por @Joe C es engañosa.

Permítanme intentar explicar la diferencia entre thenApplyy thenComposecon un ejemplo.

Supongamos que tenemos 2 métodos: getUserInfo(int userId)y getUserRating(UserInfo userInfo):

public CompletableFuture<UserInfo> getUserInfo(userId)

public CompletableFuture<UserRating> getUserRating(UserInfo)

Ambos tipos de retorno de método son CompletableFuture.

Queremos llamar getUserInfo()primero, y al finalizar, llamar getUserRating()con el resultado UserInfo.

Al finalizar el getUserInfo()método, probemos ambos thenApplyy thenCompose. La diferencia está en los tipos de devolución:

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

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

thenCompose()funciona como el de Scala,flatMap que aplana los futuros anidados.

thenApply()devolvió los futuros anidados como estaban, pero thenCompose()aplanó el anidado CompletableFuturespara que sea más fácil encadenar más llamadas a métodos.

Dorjee
fuente
2
Entonces, la respuesta de Joe C no es engañosa. Es correcto y más conciso.
koleS
2
Esto fue muy útil :-)
dstibbe
1
En mi opinión, es un diseño deficiente escribir CompletableFuture <UserInfo> getUserInfo y CompletableFuture <UserRating> getUserRating (UserInfo) \\ en su lugar debería ser UserInfo getUserInfo () e int getUserRating (UserInfo) si quiero usarlo asíncrono y en cadena, entonces puedo use ompletableFuture.supplyAsync (x => getUserInfo (userId)). thenApply (userInfo => getUserRating (userInfo)) o algo así, es más legible en mi humilde opinión, y no es obligatorio envolver TODOS los tipos de retorno en CompletableFuture
user1694306
@ user1694306 Si el diseño es deficiente o no depende de si la calificación del usuario está contenida en el UserInfo(entonces sí) o si debe obtenerse por separado, tal vez incluso costoso (entonces no).
glglgl
42

Los Javadocs actualizados en Java 9 probablemente ayudarán a comprenderlo mejor:

luego aplicar

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

Devuelve un nuevo CompletionStageque, cuando esta etapa se completa normalmente, se ejecuta con el resultado de esta etapa como argumento de la función proporcionada.

Este método es análogo a Optional.mapy Stream.map.

Consulte la CompletionStagedocumentación para conocer las reglas que cubren la finalización excepcional.

luego componer

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

Devuelve un nuevo CompletionStageque se completa con el mismo valor que CompletionStagedevuelve la función dada.

Cuando esta etapa se completa normalmente, la función dada se invoca con el resultado de esta etapa como argumento, devolviendo otro CompletionStage. Cuando esa etapa se completa normalmente, lo CompletionStagedevuelto por este método se completa con el mismo valor.

Para asegurar el progreso, la función suministrada debe organizar la finalización final de su resultado.

Este método es análogo a Optional.flatMapy Stream.flatMap.

Consulte la CompletionStagedocumentación para conocer las reglas que cubren la finalización excepcional.

Didier L
fuente
14
Me pregunto por qué no nombraron esas funciones mapy flatMapen primer lugar.
Matthias Braun
1
@MatthiasBraun Creo que eso es porque thenApply()simplemente llamará Function.apply(), y thenCompose()es un poco similar a componer funciones.
Didier L
14

thenApplyy thenComposeson métodos de CompletableFuture. Úselos cuando tenga la intención de hacer algo con CompleteableFutureel resultado de to con a Function.

thenApplyy thenComposeambos devuelven un CompletableFuturecomo resultado. Puede encadenar varios thenApplyo thenComposejuntos. Suministre a Functiona cada llamada, cuyo resultado será la entrada a la siguiente Function.

El Functionque proporcionó a veces necesita hacer algo sincrónicamente. El tipo de retorno de su Functiondebe ser un no Futuretipo. En este caso debes usar 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

En otras ocasiones, es posible que desee realizar un procesamiento asincrónico en este Function. En ese caso deberías usar thenCompose. El tipo de retorno de su Functiondebería ser un CompletionStage. El siguiente Functionen la cadena obtendrá el resultado de eso CompletionStagecomo entrada, desenvolviendo así el 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

Esta es una idea similar a la de Javascript Promise. Promise.thenpuede aceptar una función que devuelve un valor o Promiseun valor. La razón por la que estos dos métodos tienen nombres diferentes en Java se debe al borrado genérico . Function<? super T,? extends U> fny Function<? super T,? extends CompletionStage<U>> fnse consideran del mismo tipo de tiempo de ejecución - Function. Por lo tanto, thenApplyy thenComposedebe tener un nombre distinto, o el compilador de Java se quejaría de firmas de métodos idénticas. El resultado final es que Javascript Promise.thense implementa en dos partes, thenApplyy thenComposeen Java.

Puede leer mi otra respuesta si también está confundido acerca de una función relacionada thenApplyAsync.

1283822
fuente