foo.thenCompose(fooResponse -> {
...
return bar.thenCompose(barResponse -> {
...
});
}).exceptionally(e -> {
...
});
Будет ли это .exceptionally()
также перехватывать исключения, генерируемые внутри вложенной bar.thenCompose
лямбды? Или мне нужно написать это:
foo.thenCompose(fooResponse -> {
...
return bar.thenCompose(barResponse -> {
...
}).exceptionally(nestedE -> {
...
});
}).exceptionally(e -> {
...
});
Потом перезалить?
@JacobG. - Ага, коллега. Подбрасываем вопрос для самостоятельного ответа, как только узнаем. Я не мог найти вопрос, который отвечал бы на этот вопрос, как я его формулировал. Возможно, кто-то здесь может дать больше информации об этом, несмотря ни на что.
Попался. Я сделаю снимок через пару часов, если на него все еще не ответили.
Так что ответ: да, поймал. Но моя интуиция подсказывала, что синтаксически кажется, что каждая цепочка .thenApply()
, .thenCompose()
и т. д. должна заканчиваться своим собственным .exceptionally()
. Хм... Может быть, поскольку вложенный вызов возвращает будущее, которое привязывается к остальным внешним вызовам, на самом деле это всего лишь одна цепочка фьючерсов? Может быть, это интуиция, которой мне не хватает.
Да, с помощью метода «исключительно» вы можете обрабатывать вложенные 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
.
stage0
будет завершено в исключительном порядке, оно попытается завершить stage1
в исключительном порядке.stage0
завершается со значением и функция выдает исключение, она попытается завершить stage1
в исключительных случаях.stage0
завершается со значением и функция возвращает этап (someOtherStage
), который был или будет завершен в исключительном порядке, она попытается завершить stage1
в исключительном порядке.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
.
Эй, большое спасибо. Это было так полезно для сброса моего понимания.
Ты это пробовал?