Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Shortcut chain of completablefuture based on a condition

I want to skip chain of completablefutures based a specific condition. I tried the solution proposed at chaining several completionstage, but it doesn't seem to work. Here it the code:

@RunWith(JUnit4.class)
public class ExceptionHandlingTests {
@Test
public void test1() {
    CompletableFuture<Integer> result = new CompletableFuture<>();
    CompletableFuture.runAsync(() -> {
        System.out.println("Completing result1. Result: " + result.isDone());
        result.complete(10);
    }).thenCompose(x -> {
        System.out.println("Completing result2. Result: " + result.isDone());
        result.complete(10);
        return CompletableFuture.completedFuture(5);
    }).thenCompose(x -> {
        System.out.println("Completing result3. Result: " + result.isDone());
        result.complete(10);
        return CompletableFuture.completedFuture(5);
    }).applyToEither(result, Function.identity());
    }
} 

Output:

Completing result1. Result: false
Completing result2. Result: true
Completing result3. Result: true

Even though the "result" completablefuture is marked completed, subsequent completablefutures are still executed. How to skip Completablefuture 2 and 3?

like image 667
user3832281 Avatar asked Jan 29 '23 21:01

user3832281


1 Answers

You have created a dependency chain like this:

  first
    ↓  (↘)
   next  result
    ↓  ↙
  final

The (↘) is an explicit completion call result.complete(…), but all other completions happen automatically. The final stage, created via applyToEither will be completed with either of the prerequisites, whichever is completed first, but does not modify the behavior of them.

In principle, any code could invoke complete on it without affecting either of these stages that might complete the final stage otherwise. The same applies to cancellation. Calling cancel on a stage will complete the stage, your calling the method on, without affecting the stages you used to construct it.

The answer of the linked question creates stages like

    first
   (↙)  (↘)
 next 1  result
   ↓     |
 next 2  |
   ↘    ↙
    final

The key point is, the initial stage will complete either of two CompletableFutures explicitly, not triggering an automatic completion. The other chain of dependent stages will never get evaluated. Since the final stage is an applyToEither, the completion of only one of the chains is sufficient to evaluate the final function. It still does not affect the prerequisite stages.

Note that for your chain, consisting of thenCompose operations, you can implement a similar logic in a simpler fashion, due to the fact that your functions return a CompletableFuture anyway. So simply returning a new CompletableFuture that will never be completed when you want to shortcut, solves the issue. You can even rewrite your initial runAsync to use thenCompose instead:

for(int shortCutAt: IntStream.range(0, 4).toArray()) {
    System.out.println("Example execution with "
                      +(shortCutAt==0? "no shortcut": "shortcut at "+shortCutAt));

    CompletableFuture<Integer> result = new CompletableFuture<>();
    CompletableFuture.completedFuture(null).thenCompose(justVoid -> { // runAsync
        System.out.println("Completing result1. Result: " + result.isDone());
        if(shortCutAt == 1) { result.complete(10); return new CompletableFuture<>(); }
        return CompletableFuture.completedFuture(justVoid);
    }).thenCompose(x -> {
        System.out.println("Completing result2. Result: " + result.isDone());
        if(shortCutAt == 2) { result.complete(10); return new CompletableFuture<>(); }
        return CompletableFuture.completedFuture(5);
    }).thenCompose(x -> {
        System.out.println("Completing result3. Result: " + result.isDone());
        if(shortCutAt == 3) { result.complete(10); return new CompletableFuture<>(); }
        return CompletableFuture.completedFuture(5);
    })
    .applyToEither(result, Function.identity())
    .thenAccept(fr -> System.out.println("final result: "+fr));

    System.out.println();
}
Example execution with no shortcut
Completing result1. Result: false
Completing result2. Result: false
Completing result3. Result: false
final result: 5

Example execution with shortcut at 1
Completing result1. Result: false
final result: 10

Example execution with shortcut at 2
Completing result1. Result: false
Completing result2. Result: false
final result: 10

Example execution with shortcut at 3
Completing result1. Result: false
Completing result2. Result: false
Completing result3. Result: false
final result: 10
like image 149
Holger Avatar answered Feb 04 '23 12:02

Holger