Reputation: 28951
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;
Upvotes: 2
Views: 135
Reputation: 10814
Using CompletableFuture#exceptionallyCompose
, this becomes basically a one liner:
var resultFut = mainFut.exceptionallyCompose(throwable -> backupFut);
Edit: exceptionallyCompose
is available since Java 12.
Upvotes: 1
Reputation: 298153
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());
Upvotes: 2
Reputation: 311
Not sure if this code would match your coding style but would something like this work to reduce indentation levels and try and avoid nesting hell?
mainFut.handle((r, t) -> {
if (t == null) {
resultFut.complete(r); // main process is fine, use its result
return null;
}
LOGGER.error("Main process failed, using backup", t);
backupFut.handle((rb, tb) -> {
if (tb == null) {
resultFut.complete(rb); //backup is fine, use its result
return null;
}
resultFut.completeExceptionally(tb); //backup failed too
return null;
});
return null;
});
Also just a thought I'm not 100% familiar with lambda functions do you need to return values in those lambda functions? You could remove the last two return statements if not.
Upvotes: 0