Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stop CompletableFuture chaining inside the Supplier

I have such an operation,

public void createFuture() {
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 5);
    future.thenApply(i -> {
        if (i == 5) {
            System.out.println("hello, i equals to 5 so expensive operations are unnecessary");
        }
        return i;
    }).thenApply(i -> {
        if (i != 5) {
            System.out.println("Expensive operation");
        }
        return i;
    }).thenApply(i -> {
        if (i != 5) {
            System.out.println("Expensive operation");
        }
        return i;
    }).thenApply(i -> {
        if (i != 5) {
            System.out.println("Expensive operation");
        }
        return i;
    }).thenApply(i -> {
        if (i != 5) {
            System.out.println("Expensive operation");
        }
        return i;
    });

}

Here in the first block, I check some condition (i == 5) and if it is true, I don't need the rest of the operations. I don't want to throw an exception to cancel the rest because this is not an exceptional situation. Is there a nice way to do it other than passing some boolean to each operation?

like image 293
Aytug Avatar asked Mar 24 '26 23:03

Aytug


2 Answers

CompletableFuture.thenCompose() should do the trick.

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;

class Scratch {

  public static void main(String[] args) throws ExecutionException, InterruptedException {
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 5);
    CompletableFuture<Integer> composed = future.thenCompose(number -> {
      if (number == 5)
        return simpleFuture(number);
      else
        return complexFuture(number);
    });
    System.out.println(composed.get());
  }

  private static CompletionStage<Integer> complexFuture(Integer number) {
    return CompletableFuture.completedFuture(number)
        .thenApply(i -> {
          System.out.println("I am expensive");
          return i;
        });
  }

  private static CompletableFuture<Integer> simpleFuture(Integer number) {
    return CompletableFuture.completedFuture(number)
        .thenApply(i -> {
          System.out.println("I am cheap");
          return i;
        });
  }
}
like image 105
Raipe Avatar answered Mar 26 '26 12:03

Raipe


CompletableFuture does not have mechanisms to support conditional statements. In your case the code can be simplified by extracting all tasks for i != 5 into one CompletableFuture with thenCompose.

Other workaround would be to create a map, filter it and create a completable future with it:

Map<Predicate<Integer>, Runnable> m = Map.of(
    integer -> integer == 5,
    () -> System.out.println("Task 1"),
    integer -> integer != 5,
    () -> System.out.println("Task 2"),
    integer -> integer != 5,
    () -> System.out.println("Task 3")
);
CompletableFuture[] runnables = m.entrySet().stream()
    .filter(e -> e.getKey().test(5))
    .map(Map.Entry::getValue)
    .map(CompletableFuture::runAsync)
    .toArray(CompletableFuture[]::new);
CompletableFuture composed = CompletableFuture.allOf(runnables);

Here I'm creating a map Predicate -> Runnable. Then I'm filtering out all pairs that do not fulfill the predicate and wrapping runnables into CompletableFutures. At the end composed is a CompletableFuture, that is composed of all the desired runnables.

like image 42
Andronicus Avatar answered Mar 26 '26 12:03

Andronicus



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!