Одного exceptionally
в конце достаточно, чтобы заменить любое бросаемое значение альтернативным обычным значением результата, по крайней мере, для полученной им конечной стадии, но стоит очистить настрой, который привел к этому вопросу.
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
.