Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Chaining several CompletionStage only if a condition is achieved

Tags:

I have several CompletionStage methods that I'd like to chain. The problem is that the result of the first one will determine if the next ones should be executed. Right now the only way to achieve this seems to be passing "special" arguments to next CompletionStage so it doesn't execute the full code. For example:

public enum SomeResult {
    RESULT_1,
    RESULT_2,
    RESULT_3
}

public CompletionStage<SomeResult> someMethod(SomeArgument someArgument) {

    return CompletableFuture.supplyAsync(() -> {
        // loooooong operation
        if (someCondition)
            return validValue;
        else
            return null;
    }).thenCompose(result -> {
        if (result != null)
            return someMethodThatReturnsACompletionStage(result);
        else
            return CompletableFuture.completedFuture(null);
    }).thenApply(result -> {
        if (result == null)
            return ChainingResult.RESULT_1;
        else if (result.someCondition())
            return ChainingResult.RESULT_2;
        else
            return ChainingResult.RESULT_3;
    });
}

Since the whole code depends on the first someCondition (if it's false then the result will be RESULT_1, if not then the whole code should be executed) this construction looks a bit ugly to me. Is there any way to decide if 2nd (thenCompose(...)) and 3rd (thenApply(...)) methods should be executed?

like image 995
Alberto S. Avatar asked Apr 04 '16 15:04

Alberto S.


People also ask

What is the use of completedFuture?

If we already know the result of a computation, we can use the static completedFuture method with an argument that represents a result of this computation. Consequently, the get method of the Future will never block, immediately returning this result instead: Future<String> completableFuture = CompletableFuture.

What is completedFuture in Java?

Overview. completedFuture() is a static method of the CompletableFuture class and is used to get a new CompletableFuture that is in the completed stage, with the passed value as the result of the future. The completedFuture method is defined in the CompletableFuture class.

What is a Completable future?

What's a CompletableFuture? CompletableFuture is used for asynchronous programming in Java. Asynchronous programming is a means of writing non-blocking code by running a task on a separate thread than the main application thread and notifying the main thread about its progress, completion or failure.


Video Answer


2 Answers

For the sake of completeness I'm adding a new answer

Although the solution proposed by @Holger works great it's kinda strange to me. The solution I've been using involves separating different flows in different method calls and chaining them with thenCompose:

public enum SomeResult {
    RESULT_1,
    RESULT_2,
    RESULT_3
}

public CompletionStage<SomeResult> someMethod(SomeArgument someArgument) {

    return CompletableFuture.supplyAsync(() -> {
        // loooooong operation
        if (someCondition)
            return operateWithValidValue(value);
        else
            return CompletableFuture.completedValue(ChainingResult.RESULT_1);
    })
        .thenCompose(future -> future);

public CompletionStage<SomeResult> operateWithValidValue(... value) {
     // more loooong operations...
     if (someCondition)
         return CompletableFuture.completedValue(SomeResult.RESULT_2);
     else
         return doFinalOperation(someOtherValue);   
}

public CompletionStage<SomeResult> doFinalOperation(... value) {
     // more loooong operations...
     if (someCondition)
         return CompletableFuture.completedValue(SomeResult.RESULT_2);
     else
         return CompletableFuture.completedValue(SomeResult.RESULT_3);
}

NOTE: I've changed the algorithm from the question in sake of a more complete answer

All long operations could be potentially wrapped inside another CompletableFuture.supplyAsync with little effort

like image 36
Alberto S. Avatar answered Sep 18 '22 08:09

Alberto S.


You can do it like this:

public CompletionStage<SomeResult> someMethod(SomeArgument someArgument) {
    CompletableFuture<SomeResult> shortCut = new CompletableFuture<>();
    CompletableFuture<ResultOfFirstOp> withChain = new CompletableFuture<>();

    CompletableFuture.runAsync(() -> {
        // loooooong operation
        if (someCondition)
            withChain.complete(validValue);
        else
            shortCut.complete(SomeResult.RESULT_1);
    });
    return withChain
        .thenCompose(result -> someMethodThatReturnsACompletionStage(result))
        .thenApply(result ->
                   result.someCondition()? SomeResult.RESULT_2: SomeResult.RESULT_3)
        .applyToEither(shortCut, Function.identity());
}

Instead of one CompletableFuture we create two, representing the different execution paths we might take. The loooooong operation is submitted as runnable then and will deliberately complete one of these CompletableFuture. The followup stages are chained to the stage representing the fulfilled condition, then both execution paths join at the last applyToEither(shortCut, Function.identity()) step.

The shortCut future has already the type of the final result and will be completed with the RESULT_1, the result of your nullpassing path, which will cause the immediate completion of the entire operation. If you don’t like the dependency between the first stage and the actual result value of the short-cut, you can retract it like this:

public CompletionStage<SomeResult> someMethod(SomeArgument someArgument) {
    CompletableFuture<Object> shortCut = new CompletableFuture<>();
    CompletableFuture<ResultOfFirstOp> withChain = new CompletableFuture<>();

    CompletableFuture.runAsync(() -> {
        // loooooong operation
        if (someCondition)
            withChain.complete(validValue);
        else
            shortCut.complete(null);
    });
    return withChain
        .thenCompose(result -> someMethodThatReturnsACompletionStage(result))
        .thenApply(result ->
                   result.someCondition()? SomeResult.RESULT_2: SomeResult.RESULT_3)
        .applyToEither(shortCut.thenApply(x -> SomeResult.RESULT_1), Function.identity());
}

If your third step wasn’t exemplary but looks exactly like shown in the question, you could merge it with the code path joining step:

public CompletionStage<SomeResult> someMethod(SomeArgument someArgument) {
    CompletableFuture<ResultOfSecondOp> shortCut = new CompletableFuture<>();
    CompletableFuture<ResultOfFirstOp> withChain = new CompletableFuture<>();

    CompletableFuture.runAsync(() -> {
        // loooooong operation
        if (someCondition)
            withChain.complete(validValue);
        else
            shortCut.complete(null);
    });
    return withChain
        .thenCompose(result -> someMethodThatReturnsACompletionStage(result))
        .applyToEither(shortCut, result -> result==null? SomeResult.RESULT_1:
            result.someCondition()? SomeResult.RESULT_2: SomeResult.RESULT_3);
}

then we only skip the second step, the someMethodThatReturnsACompletionStage invocation, but that can still stand for a long chain of intermediate steps, all skipped without the need to roll out a manual skipping via nullcheck.

like image 50
Holger Avatar answered Sep 20 '22 08:09

Holger