.exceptionally()会捕获嵌套期货中抛出的异常吗?或者放在哪里正确.exceptionally()

问题描述 投票:1回答:2
foo.thenCompose(fooResponse -> {
  ...
  return bar.thenCompose(barResponse -> {
    ...
  });
}).exceptionally(e -> {
  ...
});

这个.exceptionally()是否也会捕获嵌套的bar.thenCompose lambda中抛出的异常?或者我需要写这个:

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

然后重新抛出?

java exception java-8 completable-future
2个回答
4
投票

最后一个exceptionally足以用替代的普通结果值替换任何throwable,至少对于它返回的结果阶段,但是值得清理导致这个问题的思维模式。

exceptionally没有任何例外情况,也没有嵌套期货。重要的是要理解CompletionStage定义的所有方法都会创建一个新的完成阶段,其完成将受特定方法的契约影响,但从不影响完成阶段,该方法已被调用。

因此,当你使用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没有捕获异常,前一阶段的特殊完成永远不会改变,它所做的一切,就是定义一个新阶段的完成。

因此,如果stage1的结果是stage0.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将永远不会被完成并且函数永远不会被评估,因此,“嵌套”阶段将永远不会被创建,但正如所说,这段历史与stage2无关。

因此,如果您最后一次调用链接完成阶段是exceptionally(function),它将返回一个新阶段,该阶段将始终使用前一阶段或从function返回的值完成,而不管它们之前的依赖关系图如何。除非function本身抛出一个异常,否则有人会调用其中一个明确的完成方法,比如cancel


0
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.