.exceptionally()是否会捕获嵌套的Future中抛出的异常?或者在哪里放置.exceptionally()是正确的

时间:2019-04-26 19:43:28

标签: java exception java-8 future completable-future

foo.thenCompose(fooResponse -> {
  ...
  return bar.thenCompose(barResponse -> {
    ...
  });
}).exceptionally(e -> {
  ...
});

.exceptionally()还会捕获嵌套bar.thenCompose lambda中引发的异常吗?还是我需要这样写:

foo.thenCompose(fooResponse -> {
  ...
  return bar.thenCompose(barResponse -> {
    ...
  }).exceptionally(nestedE -> {
    ...
  });
}).exceptionally(e -> {
  ...
});

然后重新抛出?

2 个答案:

答案 0 :(得分:4)

最后一个exceptionally足以用可替代的普通结果值替换任何throwable,至少对于它返回的结果阶段而言,但这值得清理引发此问题的思维定势。

exceptionally不会捕获任何异常,并且也没有嵌套期货。重要的是要了解,CompletionStage定义的所有方法都创建了一个 new 完成阶段,该完成阶段将受特定方法的合同影响,但从不影响完成阶段,该方法已经调用。

因此,当您使用exceptionally时,涉及到两个期货,一个是您特别调用的期货,另一个是exceptionally返回的新期货。合同规定,在通常完成的情况下,后者的价值将与前者相同,但如果前者已经完成,则具有功能评估的结果。

所以当您执行

for(int run = 0; run < 4; run++) {
    CompletableFuture<String> stage1 = new CompletableFuture<>();
    CompletableFuture<String> stage2 = stage1.exceptionally(t -> "alternative result");

    if(run > 1) stage2.cancel(false);

    if((run&1) == 0) stage1.complete("ordinary result");
    else stage1.completeExceptionally(new IllegalStateException("some failure"));

    stage1.whenComplete((v,t) ->
        System.out.println("stage1: "+(t!=null? "failure "+t: "value "+v)));
    stage2.whenComplete((v,t) ->
        System.out.println("stage2: "+(t!=null? "failure "+t: "value "+v)));
    System.out.println();
}

它将打印:

stage1: value ordinary result
stage2: value ordinary result

stage1: failure java.lang.IllegalStateException: some failure
stage2: value alternative result

stage1: value ordinary result
stage2: failure java.util.concurrent.CancellationException

stage1: failure java.lang.IllegalStateException: some failure
stage2: failure java.util.concurrent.CancellationException

表明第一阶段总是反映我们明确完成的结果,而不管第二阶段发生了什么。因此exceptionally不会捕获异常,上一阶段的特殊完成永远不会改变,它所做的只是定义新阶段的完成。

因此,如果stage1stage0.thenCompose(x -> someOtherStage)通话的结果,则对stage1stage2之间的关系并不重要。重要的是stage1的完成。

  1. 如果stage0异常完成,它将尝试异常完成stage1
  2. 如果stage0用一个值完成,并且该函数引发异常,它将尝试异常完成stage1
  3. 如果stage0用一个值完成,并且该函数返回一个已经或将要异常完成的阶段(someOtherStage),它将尝试以异常方式完成stage1
  4. 如果stage0用一个值完成,并且该函数返回一个已经或将要用一个值完成的阶段(someOtherStage),它将尝试用该值完成stage1

请注意,没有嵌套,someOtherStage可能是新建的或已经存在的阶段,它也可能在其他地方使用。由于链接总是构建不影响现有阶段的新阶段,因此这些其他地方不会受到此处发生的任何事情的影响。

请进一步注意术语“尝试完成”,因为我们仍可以在尝试之前在complete上调用completeExceptionallycancelstage1。对于stage2来说,完成的方式与结果无关紧要。

因此,如果从1.到3.的任何一种情况的尝试异常成功地完成stage1都成功,则将尝试完成stage2并将函数结果传递给exceptionally。在情况4中,如果尝试成功完成一个值的stage1,将尝试尝试完成该值的stage2

如果我们使用

,则可以证明前一阶段的历史无关紧要。
CompletableFuture<String> stage1 = new CompletableFuture<>();
CompletableFuture<String> stage2 = stage1.thenCompose(s -> new CompletableFuture<>());
CompletableFuture<String> stage3 = stage2.exceptionally(t -> "alternative result");

stage1.complete("ordinary result"); // you can omit this line if you want
stage2.completeExceptionally(new IllegalStateException("some failure"));

stage3.whenComplete((v,t) ->
    System.out.println("stage3: "+(t!=null? "failure "+t: "value "+v)));

它将打印stage3: value alternative result,这是由于stage2已异常完成,而完成的历史完全不相关。 stage1.complete("ordinary result");语句将导致函数评估返回一个新的CompletableFuture,它将永远不会完成,因此不会对结果有所帮助。如果我们省略此行,stage1将永远不会完成,函数也不会被评估,因此,“嵌套”阶段将永远不会创建,但是正如所说的,此历史记录对{{1}而言并不重要}。

因此,如果您最后一次链接完成阶段的调用是stage2,它将返回一个新阶段,该阶段将始终以上一个阶段或从exceptionally(function)返回的值来完成,不管它们之前的依赖图是什么样子。除非function本身抛出异常,否则除非有人调用它上的一种显式完成方法,例如function

答案 1 :(得分:0)

是的,通过“ exceptionally”方法,您可以处理嵌套的CompletableFuture 第一个示例将正常工作