CompletableFuturo | luego Aplicar vs luego Componer

11 minutos de lectura

avatar de usuario
chicoT

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 CompletionStage que, cuando esta etapa finaliza normalmente, se ejecuta con el resultado de esta etapa como argumento de la función suministrada.

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

Devuelve un nuevo CompletionStage que, cuando esta etapa finaliza normalmente, se ejecuta con esta etapa como argumento de la función suministrada.

Entiendo que el segundo argumento de thenCompose extiende el CompletionStage donde thenApply no es.

¿Podría alguien dar un ejemplo en cuyo caso tengo que usar thenApply y cuando thenCompose?

  • ¿Entiendes la diferencia entre map y flatMap en Stream? thenApply es el map y thenCompose es el flatMap de CompletableFuture. Tu usas thenCompose para evitar tener CompletableFuture<CompletableFuture<..>>.

    – Misha

    25 de marzo de 2017 a las 23:29

  • Esta es una muy buena guía para comenzar con CompletableFuture: baeldung.com/java-completablefuture

    – el alquimista

    19 mayo 2020 a las 13:00

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

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

thenCompose se usa si tiene una función de mapeo asincrónico (es decir, una que devuelve un 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));

  • ¿Por qué un programador debería usar .thenCompose(x -> CompletableFuture.supplyAsync(() -> x+1)) en vez de .thenApplyAsync(x -> x+1)? Ser sincrónico o asincrónico es no la diferencia relevante.

    – Holger

    22 de enero de 2018 a las 12:17

  • No lo harían así. Sin embargo, si una biblioteca de terceros que usaron devolvió un CompletableFutureentonces aquí sería donde thenCompose sería la manera de aplanar la estructura.

    – Joe C.

    22 de enero de 2018 a las 20:44


  • @Holger lee mi otra respuesta si estás confundido acerca de thenApplyAsync porque no es lo que crees que es.

    – 1283822

    27 de julio de 2018 a las 15:47

  • @ 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

    27 de julio de 2018 a las 15:55

  • Sinceramente, creo que es un mejor ejemplo de código que tiene AMBAS funciones de sincronización y asíncrona con AMBAS .supplyAsync().thenApply() y .supplyAsync(). thenCompose() debe proporcionarse para explicar el concepto (4 futuros en lugar de 2).

    – Oleksandr Berezianskyi

    15 de agosto de 2018 a las 11:24

avatar de usuario
dorjee

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

Déjame tratar de explicar la diferencia entre thenApply y thenCompose con 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 terminar, llamar getUserRating() con el resultado UserInfo.

Al finalizar getUserInfo() método, probemos ambos thenApply y 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 de Scala flatMap que aplana los futuros anidados.

thenApply() devolvió los futuros anidados tal como estaban, pero thenCompose() aplanado el anidado CompletableFutures para que sea más fácil encadenarle más llamadas a métodos.

  • Entonces la respuesta de Joe C no es engañosa. Es correcto y más conciso.

    – koles

    25 de octubre de 2018 a las 13:29

  • En mi humilde opinión, es un diseño deficiente escribir CompletableFuture getUserInfo y CompletableFuture 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 como esto, es más legible en mi humilde opinión, y no es obligatorio envolver TODOS los tipos de devolución en CompletableFuture

    – usuario1694306

    16 de diciembre de 2019 a las 22:11


  • @ user1694306 Si se trata de un diseño deficiente o no, depende de si la calificación del usuario está contenida en el UserInfo (entonces sí) o si tiene que obtenerse por separado, tal vez incluso costoso (entonces no).

    – glglgl

    11 de febrero de 2020 a las 8:36

avatar de usuario
didier l

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

entonces Aplicar

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

Devuelve un nuevo CompletionStage que, cuando esta etapa finaliza normalmente, se ejecuta con el resultado de esta etapa como argumento de la función suministrada.

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

Ver el CompletionStage documentación para las reglas que cubren la finalización excepcional.

luego componer

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

Devuelve un nuevo CompletionStage que se completa con el mismo valor que el CompletionStage devuelto por 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 otra
CompletionStage. Cuando esa etapa se completa normalmente, el
CompletionStage devuelto por este método se completa con el mismo valor.

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

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

Ver el CompletionStage documentación para las reglas que cubren la terminación excepcional.

  • Me pregunto por qué no nombraron esas funciones. map y flatMap en primer lugar.

    – Matías Braun

    16 de junio de 2017 a las 9:08

  • @MatthiasBraun Creo que es porque thenApply() simplemente llamará Function.apply()y thenCompose() es un poco similar a componer funciones.

    – Didier L.

    16 de junio de 2017 a las 9:56

avatar de usuario
1283822

thenApply y thenCompose son métodos de CompletableFuture. Úselos cuando tenga la intención de hacer algo para CompletableFutureel resultado con un Function.

thenApply y thenCompose ambos devuelven un CompletableFuture como su propio resultado. Puedes encadenar varios thenApply o thenCompose juntos. Suministre un Function a cada llamada, cuyo resultado será la entrada a la siguiente Function.

los Function usted proporcionó a veces necesita hacer algo sincrónicamente. El tipo de retorno de su Function debe ser un no-Future escribe. 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

Otras veces, es posible que desee realizar un procesamiento asíncrono en este Function. En ese caso deberías usar thenCompose. El tipo de retorno de su Function debería ser un CompletionStage. El siguiente Function en la cadena obtendrá el resultado de eso CompletionStage como 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.then puede aceptar una función que devuelve un valor o un Promise de un 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> fn y Function<? super T,? extends CompletionStage<U>> fn se consideran el mismo tipo de Runtime – Function. De este modo thenApply y thenCompose tiene que tener un nombre distinto, o el compilador de Java se quejaría de firmas de métodos idénticas. El resultado final es Javascript Promise.then se implementa en dos partes: thenApply y thenCompose – en Java.

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

luego componer () es mejor para encadenar CompletableFuture.

entonces Aplicar() es mejor para transformar el resultado del futuro Completable.

Puede lograr su objetivo utilizando ambas técnicas, pero una es más adecuada para un caso de uso que para otro.

public CompletableFuture<Integer> process(Integer i) {
    CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(
            () -> new HeavyTask(i).execute());
    return completableFuture;
}

@SneakyThrows
public CompletableFuture<Integer> thenApplyVsThenCompose() {
    // each time calling thenApply() function returns CompletionState
    // so you will get nested Futures 
    // you can think about it like map() java optional
    CompletableFuture<Future<Integer>> cf1 = CompletableFuture.supplyAsync(
            () -> new HeavyTask().execute())
            .thenApply(i -> process(i));

    // to get result you will have to get nested get() calls
    Integer resultFromThenApply = cf1.get().get();

    // when calling thenCompose() nested Futures are flatten
    // you can think about it like flatMap() java optional
    CompletableFuture<Integer> cf2;
    cf2 = CompletableFuture.supplyAsync(
            () -> new HeavyTask().execute())
            .thenCompose(this::process);

    // you have to just call one get() since thenCompose was flatten
    Integer resultFromThenCompose = cf2.get();
    return null;
} 

Otro problema que puede visualizar la diferencia entre esos dos

¿Cómo implementaría la solución cuando no sabe cuántas veces tiene que aplicar thenApply()/thenCompose() (en el caso de métodos recursivos, por ejemplo)?

public void nested() throws ExecutionException, InterruptedException {
    CompletableFuture<Integer> completableFutureToCompose = CompletableFuture.completedFuture(1);
    for (int i = 0; i < 10; i++) {
        log.info("Composing");
        completableFutureToCompose = completableFutureToCompose.thenCompose(this::process);
    }
    completableFutureToCompose.get();

    // not achievable using then apply
    CompletableFuture<Integer> completableFutureToApply = CompletableFuture.completedFuture(1);
    for (int i = 0; i < 10; i++) {
        log.info("Applying");
        completableFutureToApply = completableFutureToApply.thenApply(this::process).get();
    }
    completableFutureToCompose.get();
}

public CompletableFuture<Integer> process(Integer i) {
    log.info("PROCESSING");
    CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(
            () -> new HeavyTask(i).execute());
    return completableFuture;
}
  • Usando la composición, primero crea una receta de cómo los futuros se pasan de uno a otro y luego ejecuta
  • Al usar apply, ejecuta la lógica después de cada invocación de apply

  • Cuando ejecuto su segundo código, tiene el mismo resultado System.out.println(“Applying”+completableFutureToApply.get()); y System.out.println(“Componiendo”+completableFutureToCompose.get()); , el comentario al final de su publicación sobre el tiempo de ejecución de la tarea es correcto, pero el resultado de get () es el mismo, ¿puede explicar la diferencia? Gracias.

    – vuhoanghiep1993

    22 de diciembre de 2021 a las 9:36

avatar de usuario
aderchox

La respuesta de JoeC es correcta, pero creo que la mejor comparación que puede aclarar el propósito de la luego componer es la comparación entre entonces Aplicar y entonces Aplicar! Una vez cuando se le pasa un mapeo síncrono y una vez cuando se le pasa un mapeo asíncrono.

Si la asignación pasó a la entonces Aplicar devuelve un Cuerda(un no futuro, por lo que el mapeo es síncrono), entonces su resultado será CompletableFuture<String>. Ahora, de manera similar, ¿cuál será el resultado de la entonces Aplicarcuando el mapeo pasó al devuelve un CompletableFuturo> (un futuro, por lo que el mapeo es asíncrono)? El resultado final será CompletableFuture<CompletableFuture<String>>, que es un anidamiento innecesario (¡el futuro del futuro sigue siendo futuro!). Aquí es donde podemos usar luego componer para poder “componer” (anidar) múltiples tareas asincrónicas entre sí sin obtener futuros anidados en el resultado.

  • Cuando ejecuto su segundo código, tiene el mismo resultado System.out.println(“Applying”+completableFutureToApply.get()); y System.out.println(“Componiendo”+completableFutureToCompose.get()); , el comentario al final de su publicación sobre el tiempo de ejecución de la tarea es correcto, pero el resultado de get () es el mismo, ¿puede explicar la diferencia? Gracias.

    – vuhoanghiep1993

    22 de diciembre de 2021 a las 9:36

avatar de usuario
lhs295988029

privado void test1() lanza ExecutionException, InterruptedException {

    //thenApply返回的是之前的CompletableFuture
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 1)
            .thenApply((x) -> {
                x = x + 1;
                log.info("thenApply, 1, x:{}", x);
                return x;
            });

    System.out.println(future.get());
}

//thenCompose返回的是新的CompletableFuture
private void test2() throws ExecutionException, InterruptedException {
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 1)
            .thenCompose((x) -> {
                return CompletableFuture.supplyAsync(() -> {
                    Integer y = x + 1;
                    log.info("thenCompose, 1, x:{}", y);
                    return y;
                });
            });

    System.out.println(future.get());
}

  • Su respuesta podría mejorarse con información de apoyo adicional. Edite para agregar más detalles, como citas o documentación, para que otros puedan confirmar que su respuesta es correcta. Puede encontrar más información sobre cómo escribir buenas respuestas en el centro de ayuda.

    – Comunidad
    Bot

    16 de noviembre de 2021 a las 9:11

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad