CompletableFuture | thenApply vs thenCompose

119

Não consigo entender a diferença entre thenApply() e thenCompose().

Então, alguém poderia fornecer um caso de uso válido?

Dos documentos Java:

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

Retorna um novo CompletionStageque, quando este estágio é concluído normalmente, é executado com o resultado desse estágio como o argumento para a função fornecida.

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

Retorna um novo CompletionStageque, quando este estágio é concluído normalmente, é executado com este estágio como o argumento da função fornecida.

Eu entendo que o segundo argumento de thenComposeestende o CompletionStage onde thenApplynão.

Alguém poderia dar um exemplo em que caso devo usar thenApplye quando thenCompose?

GuyT
fonte
39
Você entende a diferença entre mape flatMapem Stream? thenApplyé o mape thenComposeé o flatMapde CompletableFuture. Você usa thenComposepara evitar ter CompletableFuture<CompletableFuture<..>>.
Misha
2
@Misha Obrigado pelo seu comentário. Sim, eu sei a diferença entre mape flatMape entendi. Obrigado novamente :)
GuyT
Este é um guia muito bom para começar com CompletableFuture - baeldung.com/java-completablefuture
thealchemist

Respostas:

168

thenApply é usado se você tiver uma função de mapeamento síncrono.

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

thenComposeé usado se você tiver uma função de mapeamento assíncrona (ou seja, uma que retorna a CompletableFuture). Em seguida, ele retornará um futuro com o resultado diretamente, em vez de um futuro aninhado.

CompletableFuture<Integer> future = 
    CompletableFuture.supplyAsync(() -> 1)
                     .thenCompose(x -> CompletableFuture.supplyAsync(() -> x+1));
Joe C
fonte
14
Por que um programador deveria usar em .thenCompose(x -> CompletableFuture.supplyAsync(() -> x+1))vez de .thenApplyAsync(x -> x+1)? Ser síncrono ou assíncrono não é a diferença relevante.
Holger
17
Eles não fariam isso assim. No entanto, se uma biblioteca de terceiros que eles usaram retornasse um CompletableFuture, então este seria thenComposeo caminho para nivelar a estrutura.
Joe C
1
@ArunavSanyal, os votos mostram uma imagem diferente. Esta resposta é clara e concisa.
Alex Shesterov
@Holger leu minha outra resposta se estiver confuso thenApplyAsyncporque não é o que você pensa que é.
1283822
@ 1283822 Não sei o que te faz pensar que eu estava confuso e não há nada em sua resposta que apóie sua afirmação de que “não é o que você pensa que é”.
Holger
49

Acho que a resposta postada por @Joe C é enganosa.

Deixe-me tentar explicar a diferença entre thenApplye thenComposecom um exemplo.

Vamos supor que temos 2 métodos: getUserInfo(int userId)e getUserRating(UserInfo userInfo):

public CompletableFuture<UserInfo> getUserInfo(userId)

public CompletableFuture<UserRating> getUserRating(UserInfo)

Ambos os tipos de retorno de método são CompletableFuture.

Queremos chamar getUserInfo()primeiro e, ao concluir, chamar getUserRating()com o resultado UserInfo.

Na conclusão do getUserInfo()método, vamos tentar ambos thenApplye thenCompose. A diferença está nos tipos de retorno:

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

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

thenCompose()funciona como Scala,flatMap que nivela futuros aninhados.

thenApply()retornou os futuros aninhados como estavam, mas thenCompose()achatou os aninhados CompletableFuturespara que seja mais fácil encadear mais chamadas de método para ele.

Dorjee
fonte
2
Então, a resposta de Joe C não é enganosa. É correto e mais conciso.
koleS
2
Isso foi muito útil :-)
dstibbe
1
Mesmo assim, é um design pobre escrever CompletableFuture <UserInfo> getUserInfo e CompletableFuture <UserRating> getUserRating (UserInfo) \\ em vez disso, deveria ser UserInfo getUserInfo () e int getUserRating (UserInfo) se eu quiser usá-lo assíncrono e em cadeia, então posso use ompletableFuture.supplyAsync (x => getUserInfo (userId)). thenApply (userInfo => getUserRating (userInfo)) ou qualquer coisa assim, é mais legível imho, e não obrigatório envolver TODOS os tipos de retorno em CompletableFuture
user1694306
@ user1694306 Se é um design ruim ou não, depende se a avaliação do usuário está contida no UserInfo(então sim) ou se deve ser obtida separadamente, talvez até custosa (então não).
glglgl
42

Os Javadocs atualizados em Java 9 provavelmente ajudarão a entendê-lo melhor:

thenApply

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

Retorna um novo CompletionStageque, quando este estágio é concluído normalmente, é executado com o resultado desse estágio como o argumento para a função fornecida.

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

Consulte a CompletionStagedocumentação para regras que abrangem a conclusão excepcional.

thenCompose

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

Retorna um novo CompletionStageque é concluído com o mesmo valor que o CompletionStageretornado pela função fornecida.

Quando esse estágio é concluído normalmente, a função fornecida é chamada com o resultado desse estágio como argumento, retornando outro CompletionStage. Quando esse estágio é concluído normalmente, o CompletionStageretornado por esse método é concluído com o mesmo valor.

Para garantir o progresso, a função fornecida deve providenciar a conclusão eventual de seu resultado.

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

Consulte a CompletionStagedocumentação para regras que abrangem a conclusão excepcional.

Didier L
fonte
14
Eu me pergunto por que eles não nomearam essas funções mape flatMapem primeiro lugar.
Matthias Braun
1
@MatthiasBraun Acho que é porque thenApply()simplesmente chamará Function.apply(), e thenCompose()é um pouco semelhante às funções de composição.
Didier L
14

thenApplye thenComposesão métodos de CompletableFuture. Use-os quando quiser fazer algo para obter CompleteableFutureo resultado de a Function.

thenApplye thenComposeambos retornam a CompletableFuturecomo seu próprio resultado. Você pode encadear vários thenApplyou thenComposejuntos. Forneça um Functionpara cada chamada, cujo resultado será a entrada para a próxima Function.

O que Functionvocê forneceu às vezes precisa fazer algo de forma síncrona. O tipo de retorno de seu Functiondeve ser um não Futuretipo. Neste caso, você deve 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

Outras vezes, você pode querer fazer processamento assíncrono neste Function. Nesse caso, você deve usar thenCompose. O tipo de retorno de seu Functiondeve ser a CompletionStage. O próximo Functionna cadeia obterá o resultado disso CompletionStagecomo entrada, desembrulhando o 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 é uma ideia semelhante à do Javascript Promise. Promise.thenpode aceitar uma função que retorna um valor ou Promisede um valor. A razão pela qual esses dois métodos têm nomes diferentes em Java é devido ao apagamento genérico . Function<? super T,? extends U> fne Function<? super T,? extends CompletionStage<U>> fnsão considerados o mesmo tipo de tempo de execução - Function. Portanto, thenApplye thenComposedeve ser nomeado de forma distinta, ou o compilador Java reclamaria sobre assinaturas de método idênticas. O resultado final é que o Javascript Promise.thené implementado em duas partes - thenApplye thenCompose- em Java.

Você pode ler minha outra resposta se também estiver confuso sobre uma função relacionada thenApplyAsync.

1283822
fonte