【问题标题】:How to throw a custom exception from CompletableFuture?如何从 CompletableFuture 抛出自定义异常?
【发布时间】:2018-08-08 09:07:29
【问题描述】:

问题:如何直接从.exceptionally() 抛出自定义异常?

List<CompletableFuture<Object>> futures =
    tasks.stream()
        .map(task -> CompletableFuture.supplyAsync(() -> businessLogic(task))
        .exceptionally(ex -> {
                if (ex instanceof BusinessException) return null;

                //TODO how to throw a custom exception here??
                throw new BadRequestException("at least one async task had an exception");
        }))
        .collect(Collectors.toList());

try {
    List<Object> results = futures.stream()
        .map(CompletableFuture::join)
        .collect(Collectors.toList());
} catch (CompletionException e) {
        if (e.getCause() instanceof RuntimeException) {
              throw (RuntimeException) e.getCause();
        }
        throw new RuntimeException(e.getCause());
}

问题:我总是得到一个CompletionException,其ex.getCause() 是instanceof BadRequestException。

这可能吗?

【问题讨论】:

  • 我觉得不可能,每一个异常都会被包装成一个CompletionException
  • CompletableFuture 最终将调用encodeThrowable(Throwable x),其中x 是您的例外并返回new AltResult((x instanceof CompletionException) ? x : new CompletionException(x)); - 因此您将始终得到CompletionException。我看到的唯一能直接得到你的例外的就是让它扩展CompletionException。
  • 所以我必须将上面的代码包装在一个额外的try {..} catch (CompletionException e) 中并在外面重新抛出底层异常?请查看我的更新:这使得它非常样板,但可能没有更好的解决方案?
  • 也可以使用ex.addSuppressed(new BadRequestException("at least one async task had an exception")),然后访问Arrays.stream(e.getSuppressed()).filter(...)
  • 这不是更少的样板......

标签: java java-stream completable-future


【解决方案1】:

作为said by Didier L,函数抛出的异常(或者通常是exceptions that completedCompletableFuture)总是包装在CompletionException中(除非它们已经是CompletionException或CancellationException)。

但请注意,即使不尝试通过exceptionally 翻译异常,您的代码也会变得更加简单:

List<CompletableFuture<Object>> futures =
    tasks.stream()
        .map(task -> CompletableFuture.supplyAsync(() -> businessLogic(task)))
        .collect(Collectors.toList());
try {
    List<Object> results = futures.stream()
        .map(CompletableFuture::join)
        .collect(Collectors.toList());
} catch (CompletionException e) {
    throw e.getCause() instanceof BusinessException?
        new BadRequestException("at least one async task had an exception"): e;
}

或

… catch (CompletionException e) {
    throw e.getCause() instanceof BusinessException?
        new BadRequestException("at least one async task had an exception"):
        e.getCause() instanceof BusinessException? (RuntimeException)e.getCause(): e;
}

由于exceptionally 的主要目的是将异常转换为非异常结果值,因此使用它将异常转换为另一个抛出的异常并不是最合适的,它还需要instanceof。因此,在catch 子句中执行此翻译可以省去另一个翻译步骤。

【讨论】:

  • 好吧,但正如你在上面看到的,我只想在 instanceof BusinessException', and ignore (=return null`) 否则异常情况下重新抛出异常。
  • 在您的问题代码中,catch 子句重新抛出所有意外异常,而不是吞下它们。为什么你想在一个地方用null 代替它们(不讨论代码的味道),而在另一个地方重新抛出?这是不一致的。
  • 在.exceptionally() 中看到我正在吞下除BusinessExecption 之外的任何异常!含义:我想忽略除BusinessExceptions 之外的任何异常。这些应该在未来之外被重新抛出(而不是作为嵌套的CompletionException 被重新抛出)。如果我不分析.exceptionally() 内部的异常,如果可以忽略异常,我将无法继续期货。因此,我不能简单地将它们全部捕捉到外面。
  • 但这很容易通过包装 businessLogic 来解决,而无需在其他任何地方做任何努力:CompletableFuture.supplyAsync(() -&gt; { try { return businessLogic(task); } catch(BusinessException ex) { return null; } })...CompletableFuture 不可能让这更简单。
  • 这很棘手,因为传递给exceptionally 的函数接收到Throwable,但只允许抛出未经检查的异常,因此重新抛出是有限的。正如答案中所说,exceptionally 旨在(始终)用后备值替换异常,而不是重新抛出。在此基础上构建重新投掷功能将非常复杂。另请参阅this answer。
【解决方案2】:

这是不可能的。 Javadoc of join() 明确指出:

完成时返回结果值,或抛出(未选中) 如果异常完成则异常。为了更好地符合使用 常见的函数形式,如果计算涉及 完成这个 CompletableFuture 抛出了异常,这个方法 抛出一个(未选中的)CompletionException 与底层 异常是其原因。

(重点是我的)

【讨论】:

    猜你喜欢
    • 2017-11-08
    • 1970-01-01
    • 2011-10-06
    • 1970-01-01
    • 2011-05-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多