Как создать собственное исключение из CompletableFuture?

Вопрос: как я могу напрямую выбросить настраиваемое исключение из .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.

Это вообще возможно?


person membersound    schedule 28.02.2018    source источник
comment
Я не думаю, что это возможно, каждое исключение будет заключено в CompletionException   -  person Eugene    schedule 28.02.2018
comment
CompletableFuture в конечном итоге вызовет encodeThrowable(Throwable x), где x ваше исключение, и вернет new AltResult((x instanceof CompletionException) ? x : new CompletionException(x)); - следовательно, вы всегда получите CompletionException. Единственное, что я вижу, чтобы напрямую получить ваше исключение, - это расширить его CompletionException.   -  person Thomas    schedule 28.02.2018
comment
Так что мне пришлось бы заключить приведенный выше код в дополнительный try {..} catch (CompletionException e) и повторно выбросить базовое исключение за пределы? Пожалуйста, посмотрите мое обновление: это делает его очень шаблонным, но, вероятно, лучшего решения нет?   -  person membersound    schedule 28.02.2018
comment
Вы также можете использовать ex.addSuppressed(new BadRequestException("at least one async task had an exception")), а затем перейти к Arrays.stream(e.getSuppressed()).filter(...)   -  person HRgiger    schedule 28.02.2018
comment
Это не менее шаблонный ...   -  person membersound    schedule 28.02.2018
comment
Почему вы создаете новый RuntimeException, чтобы обернуть причину, когда CompletionException уже является исключением времени выполнения, обертывающим причину? Просто используйте catch(CompletionException e) { throw e.getCause() instanceof RuntimeException? (RuntimeException)e.getCause(): e; }   -  person Holger    schedule 01.03.2018
comment
Поскольку обернутое исключение (= причина) может не быть RuntimeException самим собой ...   -  person membersound    schedule 01.03.2018
comment
Похоже, вы упустили суть моего комментария. Бросок RuntimeException обертывания любого метательного объекта не является улучшением по сравнению с ExecutionException обертыванием того же метательного объекта.   -  person Holger    schedule 01.03.2018
comment
Вы просто полностью изменили свой вопрос. Итак, вы хотите обработать BusinessException, заменив на null, и обработать любое другое исключение, выбрасывая BadRequestException? Это также должно быть отражено в предложении catch. Но просто скажи ... действительно ли ты этого хочешь? Это противоречит всем вашим предыдущим комментариям.   -  person Holger    schedule 01.03.2018


Ответы (2)


Как сказал Дидье L, исключения, создаваемые функциями (или обычно завершенные исключения a CompletableFuture) всегда заключаются в a 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;
}

or

… 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 избавляет вас от другого шага перевода.

person Holger    schedule 01.03.2018
comment
Что ж, но, как вы видите выше, я хочу только повторно вызвать исключение, если instanceof BusinessException', and ignore (=return null`) в противном случае в исключительных случаях. - person membersound; 01.03.2018
comment
В коде вашего вопроса предложение catch повторно генерирует все неожиданные исключения, а не принимает их. Почему вы хотите заменить их на null в одном месте (не обсуждая запах этого кода), а повторно использовать в другом? Это непоследовательно. - person Holger; 01.03.2018
comment
Смотрите, что в .exceptionally() я проглатываю любые исключения, кроме BusinessExecption! Значение: я хочу игнорировать любые исключения, кроме BusinessExceptions. Их следует повторно выбросить вне будущего (а не повторно выбросить как вложенный CompletionException). Если я не буду анализировать исключение внутри .exceptionally(), я не смогу продолжить фьючерсы, если исключение можно игнорировать. Таким образом, я не могу просто поймать их всех снаружи. - person membersound; 01.03.2018
comment
Точно. «Любой, кроме конкретного» означает «что-либо неожиданное», включая RuntimeExceptions (указывает на ошибки) или Errors (указывает на серьезные проблемы в среде). Их проглатывание - сильный запах кода. Но помимо этого, эти проблемы могут возникать и в других местах в вашей цепочке, чем в Supplier, поэтому вы можете не проглотить их, а повторно выбросить из условия catch. Это непоследовательно. - person Holger; 01.03.2018
comment
Хорошо, я понимаю, в чем дело. Тогда мой пример может быть выбран не в лучшем случае. Так что позвольте мне переписать это так, что BusinessException - это тот, который нужно игнорировать, а все неожиданные ошибки должны быть повторно выброшены. - person membersound; 01.03.2018
comment
Но это было бы так легко решить, обернув businessLogic без каких-либо дополнительных усилий: CompletableFuture.supplyAsync(() -> { try { return businessLogic(task); } catch(BusinessException ex) { return null; } })… Нет никакого способа CompletableFuture сделать это проще. - person Holger; 01.03.2018
comment
Хорошо, я просто подумал, что перехват и обработка исключения должны происходить в .exceptionall(), а не в самом supplyAsync(). - person membersound; 01.03.2018
comment
Это сложно, поскольку функция, переданная в exceptionally, получает Throwable, но ей разрешено генерировать только непроверенные исключения, поэтому повторная генерация тихо ограничена. Как сказано в ответе, exceptionally предназначен (всегда) для замены исключения запасным значением, а не для повторной генерации. Создать функцию повторного броска поверх этого было бы очень сложно. См. Также этот ответ. - person Holger; 01.03.2018
comment
Хорошо, если это так, я, вероятно, неправильно использую exceptionally() здесь и попытаюсь уловить свое деловое исключение напрямую у поставщика. - person membersound; 01.03.2018

Это невозможно. _1 _ четко заявляет:

Возвращает значение результата при завершении или генерирует исключение (непроверенное), если завершено в исключительных случаях. Чтобы лучше соответствовать использованию общих функциональных форм, если вычисление, участвующее в завершении этого CompletableFuture, вызвало исключение, этот метод выдает (не отмечен) _ 2_ с основным исключением в качестве его причины.

(курсив мой)

person Didier L    schedule 28.02.2018