Chaining several CompletionStage only if a condition is achieved

后端 未结 3 1440
无人共我
无人共我 2020-12-29 06:40

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 execut

相关标签:
3条回答
  • 2020-12-29 07:04

    If you have to check only for null values you can solve using Optional. For example you should do:

    public Bar execute(String id) {
    
          return this.getFooById(id)
                .thenCompose(this::checkFooPresent)
                .thenCompose(this::doSomethingElse)
                .thenCompose(this::doSomethingElseMore)
                .thenApply(rankRes -> new Bar(foo));
    
    }
    
    
    private Optional<Foo> getFooById(String id) {
    
        // some better logic to retrieve foo
    
        return Optional.ofNullable(foo);
    }
    
    
    private CompletableFuture<Foo> checkFooPresent(Optional<Foo> optRanking) {
    
        CompletableFuture<Foo> future = new CompletableFuture();
        optRanking.map(future::complete).orElseGet(() -> future.completeExceptionally(new Exception("Foo not present")));
        return future;
    }
    

    The checkFooPresent() receives an Optional, and if its value is null it complete exceptionally the CompletableFuture.

    Obviously you need to manage that exception, but if you have previously setted an ExceptionHandler or something similar it should come for free.

    0 讨论(0)
  • 2020-12-29 07:08

    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

    0 讨论(0)
  • 2020-12-29 07:09

    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.

    0 讨论(0)
提交回复
热议问题