Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CompletableFutures and filtering based on values that are inside

I'm in a bit of confusion right now, so I have a method that should return CompletableFuture<List<A>>

inside the method is:

CompletableFuture<List<String>> toReturn = asyncCall().thenApply(....)
.thenCompose(listOfStuff -> convertToList(listOfStuff.stream().map(
     key -> asyncCall2(key)
        .thenApply(optionalValue -> optionalValue.orElse(null))
).collect(Collectors.toList()));

and convertToList() simply joins futures to convert CompletableFuture<List<ComputableFuture<A>>> into CompletableFuture<List<A>>

Basically my intention is to filter null values that emerge from optionalValue.orElse(null) And it would be easy to do filter before collecting it all to list in the last line, but if I use it just before .collect it is working over CompletableFutures

I suspect there's a lot of restructuring I can do in my code.

EDIT:

private<T> CompletableFuture<List<T>> convertToList(List<CompletableFuture<T>> toConvert) {
    return CompletableFuture.allOf(toConvert.toArray(new CompletableFuture[toConvert.size()]))
            .thenApply(v -> toConvert.stream()
                    .map(CompletableFuture::join)
                    .collect(Collectors.toList())
            );
}
like image 608
Amir Avatar asked Oct 20 '25 20:10

Amir


1 Answers

The best way would probably be to change convertToList() so that it does not return a future of list, but of stream instead:

private <T> CompletableFuture<Stream<T>> convertToFutureOfStream(List<CompletableFuture<T>> toConvert) {
    return CompletableFuture.allOf(toConvert.stream().toArray(CompletableFuture[]::new))
            .thenApply(
                    v -> toConvert.stream()
                            .map(CompletableFuture::join)
            );
}

This will be more reusable as the method will allow better chaining and will not force the caller to work with a list, while still allowing to easily get a list with a simple collect.

You can then simply filter that stream to remove empty optionals:

CompletableFuture<List<String>> toReturn = asyncCall()
    .thenCompose(listOfStuff -> convertToFutureOfStream(
            listOfStuff.stream()
                    .map(this::asyncCall2)
                    .collect(Collectors.toList())
        )
        .thenApply(stream ->
                stream.filter(Optional::isPresent)
                        .map(Optional::get)
                        .collect(Collectors.toList())
        )

    );

You can even improve this a little further by changing convertToFutureOfStream() to take a stream as argument as well:

private <T> CompletableFuture<Stream<T>> convertToFutureOfStream(Stream<CompletableFuture<T>> stream) {
    CompletableFuture<T>[] futures = stream.toArray(CompletableFuture[]::new);
    return CompletableFuture.allOf(futures)
            .thenApply(v -> Arrays.stream(futures).map(CompletableFuture::join));
}

(unfortunately this raises an unchecked assignment warning because of the array of generic types)

Which then gives

CompletableFuture<List<String>> toReturn = asyncCall()
    .thenCompose(listOfStuff -> convertToFutureOfStream(
                listOfStuff.stream().map(this::asyncCall2)
            )
        .thenApply(stream ->
                stream.filter(Optional::isPresent)
                        .map(Optional::get)
                        .collect(Collectors.toList())
        )

    );
like image 72
Didier L Avatar answered Oct 22 '25 09:10

Didier L



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!