仅在满足条件时链接多个 CompletionStage

Posted

技术标签:

【中文标题】仅在满足条件时链接多个 CompletionStage【英文标题】:Chaining several CompletionStage only if a condition is achieved 【发布时间】:2016-07-24 05:01:27 【问题描述】:

我想链接几个CompletionStage 方法。问题是第一个的结果将决定是否应该执行下一个。现在实现这一点的唯一方法似乎是将“特殊”参数传递给下一个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,如果不是,那么应该执行整个代码)这个结构对我来说有点难看。有没有办法决定是否应该执行第二个(thenCompose(...))和第三个(thenApply(...))方法?

【问题讨论】:

【参考方案1】:

如果您只需要检查空值,您可以使用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 或类似的东西,它应该是免费的。

【讨论】:

【参考方案2】:

为了完整起见,我添加了一个新答案

虽然@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);

注意:为了获得更完整的答案,我已经更改了问题中的算法

所有长操作都可以轻松包装在另一个 CompletableFuture.supplyAsync

【讨论】:

【参考方案3】:

你可以这样做:

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

我们创建两个而不是一个CompletableFuture,代表我们可能采用的不同执行路径。 loooooong 操作被提交为可运行然后将故意完成其中一个CompletableFuture。后续阶段链接到表示满足条件的阶段,然后两个执行路径在最后一个applyToEither(shortCut, Function.identity()) 步骤连接。

shortCutfuture 已经有了最终结果的类型,会用你的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());

如果您的第三步不是示例但看起来与问题中显示的完全一样,您可以将其与代码路径连接步骤合并:

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

然后我们只跳过第二步,someMethodThatReturnsACompletionStage 调用,但这仍然可以代表一长串中间步骤,所有步骤都被跳过,而无需通过 nullcheck 推出手动跳过。

【讨论】:

它有效,谢谢!遵循相同的模式(创建多个CompletableFuture 并使用applyToEither(...))然后可以将其扩展到多个路径,对吗? 是的,您可以将其扩展到多个路径,但必须注意保持生成的代码可维护。也许它有助于将分支的逻辑封装到一个可以多次使用的实用方法中。

以上是关于仅在满足条件时链接多个 CompletionStage的主要内容,如果未能解决你的问题,请参考以下文章

仅在满足条件时如何应用edgeIgnoringSafeArea?

仅在满足条件时使用提示

Python分组;仅在满足条件时保留

仅在满足条件时才添加到字典

仅在满足条件时如何启动调试器

仅在满足某些条件时才调用 Safari Content Blocker 扩展?