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

Will .exceptionally() catch exceptions thrown from within nested futures? Or where is correct to put .exceptionally()

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

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

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

然后再抛?

是的,通过 'exceptionally' 方法你可以处理嵌套的 CompletableFuture 第一个例子将起作用

末尾的单个 exceptionally 足以用替代的普通结果值替换任何可抛出的对象,至少对于由它编辑的结果阶段 return 而言,但值得清理一下头脑导致这个问题的设置。

exceptionally 不会 捕捉到 任何异常,也没有 嵌套 期货。重要的是要理解 CompletionStage 定义的所有方法都会创建一个 new 完成阶段,其完成将受到特定方法契约的影响,但不会影响完成阶段,方法已被调用。

因此,当您使用 exceptionally 时,涉及两个未来,一个是您异常调用的未来,另一个是 return 由 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 完成了一个值并且函数 return 已经或将异常完成的阶段 (someOtherStage),它将尝试完成 stage1 异常地
  4. 如果 stage0 完成了一个值,并且函数 return 一个阶段 (someOtherStage) 已经或将要完成一个值,它将尝试完成 stage1 具有该值

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

进一步注意术语“尝试完成”,因为我们仍然可以在尝试之前在 stage1 上调用 completecompleteExceptionallycancel。对于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"); 语句将导致对函数 return 进行评估并生成一个永远不会完成的新 CompletableFuture,因此不会影响结果。如果我们省略这一行,stage1 将永远不会完成并且函数永远不会被评估,因此,“嵌套”阶段将永远不会被创建,但如前所述,这段历史对 stage2.

因此,如果您最后一次调用链式完成阶段是 exceptionally(function),它将 return 一个新阶段,该阶段将始终使用前一阶段或 return 从 function 编辑,不管它们之前的依赖关系图看起来如何。除非 function 本身抛出异常或有人对其调用显式完成方法之一,例如 cancel.