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

And*_*ong 2 java future exception java-8 completable-future

foo.thenCompose(fooResponse -> {
  ...
  return bar.thenCompose(barResponse -> {
    ...
  });
}).exceptionally(e -> {
  ...
});
Run Code Online (Sandbox Code Playgroud)

这也会.exceptionally()捕获从嵌套bar.thenComposelambda 内部抛出的异常吗?或者我需要这样写:

foo.thenCompose(fooResponse -> {
  ...
  return bar.thenCompose(barResponse -> {
    ...
  }).exceptionally(nestedE -> {
    ...
  });
}).exceptionally(e -> {
  ...
});
Run Code Online (Sandbox Code Playgroud)

然后又吐了?

Hol*_*ger 5

最后的一个足以exceptionally用替代的普通结果值替换任何可抛出的内容,至少对于它返回的结果阶段来说是这样,但它\xe2\x80\x99值得清理导致这个问题的思维定势。

\n\n

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

\n\n

因此,当您使用 时exceptionally,会涉及两个 future,一个是您在 上异常调用的\xe2\x80\x99 和一个由 所返回的新 future exceptionally。合同规定,如果是正常竣工,则后者将以与前者相同的价值竣工;如果前者是异常竣工的,则以功能评估的结果竣工。

\n\n

所以当你执行

\n\n
for(int run = 0; run < 4; run++) {\n    CompletableFuture<String> stage1 = new CompletableFuture<>();\n    CompletableFuture<String> stage2 = stage1.exceptionally(t -> "alternative result");\n\n    if(run > 1) stage2.cancel(false);\n\n    if((run&1) == 0) stage1.complete("ordinary result");\n    else stage1.completeExceptionally(new IllegalStateException("some failure"));\n\n    stage1.whenComplete((v,t) ->\n        System.out.println("stage1: "+(t!=null? "failure "+t: "value "+v)));\n    stage2.whenComplete((v,t) ->\n        System.out.println("stage2: "+(t!=null? "failure "+t: "value "+v)));\n    System.out.println();\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

它将打印:

\n\n
for(int run = 0; run < 4; run++) {\n    CompletableFuture<String> stage1 = new CompletableFuture<>();\n    CompletableFuture<String> stage2 = stage1.exceptionally(t -> "alternative result");\n\n    if(run > 1) stage2.cancel(false);\n\n    if((run&1) == 0) stage1.complete("ordinary result");\n    else stage1.completeExceptionally(new IllegalStateException("some failure"));\n\n    stage1.whenComplete((v,t) ->\n        System.out.println("stage1: "+(t!=null? "failure "+t: "value "+v)));\n    stage2.whenComplete((v,t) ->\n        System.out.println("stage2: "+(t!=null? "failure "+t: "value "+v)));\n    System.out.println();\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

表明无论第二阶段发生什么,第一阶段总是反映我们显式完成的结果。所以exceptionally\xe2\x80\x99t不会捕获异常,前一个阶段的异常完成永远不会改变,它所做的只是定义新阶段的完成。

\n\n

因此,如果stage1是 call 的结果,则 \xe2\x80\x99 对和stage0.thenCompose(x -> someOtherStage)之间的关系并不重要。重要的是完成。stage1stage2stage1

\n\n
    \n
  1. 如果stage0异常完成,它将尝试stage1异常完成
  2. \n
  3. 如果stage0完成一个值并且函数抛出异常,它将尝试stage1异常完成
  4. \n
  5. 如果stage0完成了一个值并且函数返回一个someOtherStage已经或将要异常完成的阶段 ( ),它将尝试stage1异常完成
  6. \n
  7. 如果stage0完成了一个值并且函数返回一个someOtherStage已经或将要完成一个值的阶段 ( ),它将尝试使用stage1该值完成
  8. \n
\n\n

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

\n\n

进一步注意术语 \xe2\x80\x9cattempt 来完成\xe2\x80\x9d,因为我们仍然可以在该尝试之前调用completecompleteExceptionallycancelon 。stage1对于 来说stage2,\xe2\x80\x99 以哪种方式完成并不重要,重要的是结果。

\n\n

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

\n\n

为了证明前一阶段\xe2\x80\x99s历史的无关性,如果我们使用

\n\n
CompletableFuture<String> stage1 = new CompletableFuture<>();\nCompletableFuture<String> stage2 = stage1.thenCompose(s -> new CompletableFuture<>());\nCompletableFuture<String> stage3 = stage2.exceptionally(t -> "alternative result");\n\nstage1.complete("ordinary result"); // you can omit this line if you want\nstage2.completeExceptionally(new IllegalStateException("some failure"));\n\nstage3.whenComplete((v,t) ->\n    System.out.println("stage3: "+(t!=null? "failure "+t: "value "+v)));\n
Run Code Online (Sandbox Code Playgroud)\n\n

它将stage3: value alternative result因为stage2完全异常而打印,完成的历史完全无关。该stage1.complete("ordinary result");语句将导致函数的评估返回一个CompletableFuture永远不会完成的新函数,因此对结果没有贡献。如果我们省略这一行,stage1则永远不会完成并且函数永远不会被评估,因此, \xe2\x80\x9cnested\xe2\x80\x9d 阶段将永远不会被创建,但正如所说,这个历史不会\xe2\x80\ x99t 对stage2.

\n\n

因此,如果您对链接完成阶段的最后一次调用是 an exceptionally(function),它将返回一个新阶段,该阶段将始终以一个值完成,该值要么来自前一阶段,要么从 中返回function,无论它们之前的依赖关系图看起来如何。除非function它本身抛出异常或者有人调用它的显式完成方法之一,例如cancel.

\n