CompletableFuture exceptionally() 和 handle() 吞下 RuntimeException?

CompletableFuture exceptionally() and handle() swallowing RuntimeException?

我有以下内容:

cflst.addAll(asList(
            supplyAsync(() -> mytask.getComp("comp"), executor)
                    .thenAccept(comp -> helper.mapStuff(comp))
                    .exceptionally(ex -> {
                        sout("Some Error");  // Never print
                        return null;
                    })
                    .handle((comp, throwable) -> {
                        sout("Error here" + comp); //always prints regarless of exception
                        return null;
                    }),

    ...

    ));

 CompletableFuture.allOf(cflst.toArray(new CompletableFuture[0])).join();

MyTask.getComp方法

 CompX getComp(String id) {
     client.getData(id);
  }

Client.getData()

 SomeX getData(String id) {
    throw new MyCustomRuntimeException("Blew Up");
 }
  

我以为在myComp方法抛出异常时,exceptionally()块得到了异常。但是好像没有。

此外,无论异常如何,handle() 总是被调用 !?!?!

我希望能够捕获任何异常并根据需要进行处理(重新抛出或吞下)。

更新

当我在 getComp 方法中捕获异常然后重新抛出它时,exceptionally 块被执行。但是不确定为什么它没有被执行,当异常在 getData 方法中向下抛出一级时?

handle()方法创建的stage无论有无异常都会执行,而exceptionally()stage只在有异常时才执行

我无法重现您的错误,只要未捕获到异常,exceptionally 块就会为我执行。

在我看来,您应该使用 handleexceptionally

handle() 方法使您能够在异常时提供默认值,exceptionally() 方法与 handle 类似但更简洁

在您的示例中,您正在按顺序编写 CompletableFutures。

.exceptionally() 之后,您将得到一个产生 null 而不是抛出异常的未来。因此,后续的.handle()永远不会观察到异常。

如果您希望 .handle() 获得相同的异常,您必须在调用 .exception() 的同一未来调用它。例如,使用一个变量来存储你在 .thenAccept() 之后得到的未来,然后在其上组合 .exceptionally().handle(),而不是按顺序组合。

但是,您应该在 .exceptionally().handle() 之间选择:

  • .exceptionally()通常用于return一个默认值而不是让异常通过

  • .handle() 通常用于记录,转换 and/or 像 catch 块一样包装异常;如果你想让异常通过,不要忘记重新抛出

对于像 finally 块这样的清理操作,.whenComplete() 更合适。