21

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

4

3 回答 3

18

你可以这样做:

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 已经有了最终结果的类型,会用你的传递路径的结果来完成,RESULT_1null会导致整个操作立即完成。如果你不喜欢第一阶段和捷径的实际结果值之间的依赖关系,你可以像这样收回它:

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 推出手动跳过。

于 2016-04-04T17:49:51.833 回答
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毫不费力地包裹在另一个操作中

于 2017-07-14T08:37:38.873 回答
0

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

于 2019-10-03T10:11:43.697 回答