I have two processes which should start in parallel and if the first process fails, I should use second process result. I came up with the following code. Is there any sensible way to simplify it?
final CompletableFuture<Data> mainFut = mainProcess(...);
final CompletableFuture<Data> backupFut = backupProcess(...);
final CompletableFuture<Data> resultFut = new CompletableFuture<>();
mainFut.handle((r, t) -> {
if (t == null) {
resultFut.complete(r); // main process is fine, use its result
} else {
LOGGER.error("Main process failed, using backup", t);
backupFut.handle((rb, tb) -> {
if (tb == null) {
resultFut.complete(rb); //backup is fine, use its result
} else {
resultFut.completeExceptionally(tb); //backup failed too
}
return null;
});
}
return null;
});
return resultFut;
You are not using the result of handle
, which would be perfect for your use case, e.g.
final CompletableFuture<Data> mainFut = mainProcess(...);
final CompletableFuture<Data> backupFut = backupProcess(...);
return mainFut.handle((r,t) -> t == null? r: backupFut.join());
If you want to avoid the potentially blocking join()
call, you can use
final CompletableFuture<Data> mainFut = mainProcess(...);
final CompletableFuture<Data> backupFut = backupProcess(...);
return mainFut.handle((r1, t1) -> t1 == null? mainFut: backupFut)
.thenCompose(Function.identity());
The backup operation’s result will supersede the main operation’s exception, but when both fail, you can include the original exception in the exceptional result like
return mainFut.handle((r1, t1) -> t1 == null? mainFut:
backupFut.whenComplete((r2, t2) -> { if(t2 != null) t2.addSuppressed(t1); }))
.thenCompose(Function.identity());