web-dev-qa-db-ja.com

条件が満たされた場合にのみ、複数のCompletionStageをチェーンする

連鎖したいいくつかのCompletionStageメソッドがあります。問題は、最初のものの結果が次のものを実行すべきかどうかを決定することです。現在、これを実現する唯一の方法は、「特別な」引数をne​​xt CompletionStageに渡すことです。そのため、完全なコードは実行されません。例えば:

_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;
    });
}
_

コード全体が最初のsomeConditionに依存しているため(それがfalseの場合、結果は_RESULT_1_になります。そうでない場合は、コード全体を実行する必要があります)、この構成は少し見苦しく見えます私に。 2番目の(thenCompose(...))および3番目の(thenApply(...))メソッドを実行する必要があるかどうかを判断する方法はありますか?

16
Pelocho

あなたはこのようにそれを行うことができます:

_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());
}
_

1つのCompletableFutureではなく、2つのパスを表す2つのパスを作成します。 loooooong操作は実行可能として送信され、これらのCompletableFutureの1つを意図的に完了します。フォローアップステージは、満たされた条件を表すステージにチェーンされ、両方の実行パスが最後のapplyToEither(shortCut, Function.identity())ステップで結合します。

shortCut futureにはすでに最終結果のタイプがあり、nullpassingパスの結果である_RESULT_1_で完了します。これにより、操作全体が即座に完了します。最初のステージとショートカットの実際の結果値の間の依存関係が気に入らない場合は、次のように撤回できます。

_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());
}
_

3番目のステップが典型的ではなかったが、質問に示されているように見える場合は、コードパスの結合ステップとマージできます。

_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);
}
_

次に、2番目のステップであるsomeMethodThatReturnsACompletionStage呼び出しのみをスキップしますが、それでも、nullcheckを使用して手動でスキップすることなく、すべてスキップされた中間ステップの長いチェーンを表すことができます。

14
Holger

完全を期すために、新しい答えを追加します

@Holgerによって提案されたソリューションはうまく機能しますが、それは私にはちょっと奇妙です。私が使用しているソリューションには、異なるメソッド呼び出しで異なるフローを分離し、それらを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 [〜#〜]:より完全な回答を得るために、アルゴリズムを質問から変更しました

すべての長い操作は、わずかな労力で別のCompletableFuture.supplyAsyncにラップされる可能性があります

2
Pelocho

Null値のみをチェックする必要がある場合は、Optionalを使用して解決できます。たとえば、次のようにする必要があります。

_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;
}
_

checkFooPresent()Optionalを受け取り、その値がnullの場合、例外的にCompletableFutureを完了します。

当然、その例外を管理する必要がありますが、以前にExceptionHandlerまたは同様の何かを設定したことがある場合は、無料で提供されるはずです。

0
firegloves