Будет ли .exceptionally() перехватывать исключения из вложенных фьючерсов? Или где правильно поставить .exceptionally()

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

Будет ли это .exceptionally() также перехватывать исключения, генерируемые внутри вложенной bar.thenCompose лямбды? Или мне нужно написать это:

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

Потом перезалить?

Ты это пробовал?

Jacob G. 26.04.2019 21:45

@JacobG. - Ага, коллега. Подбрасываем вопрос для самостоятельного ответа, как только узнаем. Я не мог найти вопрос, который отвечал бы на этот вопрос, как я его формулировал. Возможно, кто-то здесь может дать больше информации об этом, несмотря ни на что.

slackwing 26.04.2019 21:47

Попался. Я сделаю снимок через пару часов, если на него все еще не ответили.

Jacob G. 26.04.2019 21:58

Так что ответ: да, поймал. Но моя интуиция подсказывала, что синтаксически кажется, что каждая цепочка .thenApply(), .thenCompose() и т. д. должна заканчиваться своим собственным .exceptionally(). Хм... Может быть, поскольку вложенный вызов возвращает будущее, которое привязывается к остальным внешним вызовам, на самом деле это всего лишь одна цепочка фьючерсов? Может быть, это интуиция, которой мне не хватает.

slackwing 26.04.2019 22:04
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
4
357
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Да, с помощью метода «исключительно» вы можете обрабатывать вложенные CompletableFuture Первый пример будет работать

Ответ принят как подходящий

Одного exceptionally в конце достаточно, чтобы заменить любой throwable альтернативным обычным значением результата, по крайней мере, для возвращаемого им результирующего этапа, но стоит очистить ум, который привел к этому вопросу.

exceptionally не делает ловить никаких исключений и фьючерсов вложенный тоже нет. Важно понимать, что все методы, определенные CompletionStage, создают стадию завершения новый, на завершение которой будет влиять контракт конкретного метода, но никогда не влияют на стадию завершения, на которой этот метод был вызван.

Поэтому, когда вы используете exceptionally, задействованы два фьючерса: тот, который вы вызываете в исключительных случаях, и новый фьючерс, возвращаемый 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) звонка, это не имеет значения для отношений между stage1 и stage2. Все, что имеет значение, это завершение stage1.

  1. Если stage0 будет завершено в исключительном порядке, оно попытается завершить stage1 в исключительном порядке.
  2. Если stage0 завершается со значением и функция выдает исключение, она попытается завершить stage1 в исключительных случаях.
  3. Если stage0 завершается со значением и функция возвращает этап (someOtherStage), который был или будет завершен в исключительном порядке, она попытается завершить stage1 в исключительном порядке.
  4. Если stage0 завершается со значением, а функция возвращает этап (someOtherStage), который был или будет завершен со значением, она попытается завершить stage1 с этим значением.

Обратите внимание, что вложенности нет, someOtherStage может быть только что построенной или уже существующей сценой, а также может использоваться в других местах. Поскольку цепочка всегда создает новые этапы, не затрагивая существующие, эти другие места не будут затронуты ничем, происходящим здесь.

Обратите внимание на термин «попытка завершения», поскольку мы все еще могли вызвать complete, completeExceptionally или cancel на stage1 перед этой попыткой. Для 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"); вызовет оценку функции, возвращающую новый CompletableFuture, который никогда не будет завершен и, следовательно, не будет способствовать результату. Если мы опустим эту строку, stage1 никогда не будет завершена и функция никогда не будет оценена, следовательно, «вложенный» этап никогда не будет создан, но, как сказано, эта история не имеет значения для stage2.

Таким образом, если вашим последним вызовом цепочек этапов завершения является exceptionally(function), он вернет новый этап, который всегда будет завершен со значением либо из предыдущего этапа, либо из function, независимо от того, как выглядит граф зависимостей перед ними. Если только function не вызовет исключение или кто-то не вызовет для него один из явных методов завершения, например cancel.

Эй, большое спасибо. Это было так полезно для сброса моего понимания.

slackwing 01.05.2019 13:12

Другие вопросы по теме