Reputation: 375
CompletableFuture<String> future = CompletableFuture.completedFuture(null);
for(int _retrial = 1 ; _retrial <= getRetrialCountBasedOnSomeLogic() ; _retrial++) {
int finalRetrial = _retrial;
future = future
.thenCompose(lastRetrialStatus -> {
if(Strings.isNullOrEmpty(lastRetrialStatus) || (!"SUCCESS".equals(lastRetrialStatus))) {
return doSomeCalculations(finalRetrial);
} else {
return CompletableFuture.completedFuture(null);
}
})
.handle((response, throwable) -> {
if(throwable != null) {
throwable = CompletableFutures.unwrapCompletionStateException(throwable);
return throwable.getMessage();
}
return "SUCCESS";
});
}
return future;
I have recently started working on completablefutures, in the above code - As you can see I am chaining my futures with a for loop. I want the chain to stop if handle returns "SUCCESS". Something like this :
CompletableFuture<String> future = CompletableFuture.completedFuture(null);
for(int _retrial = 1 ; _retrial <= getRetrialCountBasedOnSomeLogic() ; _retrial++) {
int finalRetrial = _retrial;
future = future
.thenCompose(lastRetrialStatus -> doSomeCalculations(finalRetrial))
.handle((response, throwable) -> {
if(throwable != null) {
throwable = CompletableFutures.unwrapCompletionStateException(throwable);
return throwable.getMessage();
}
**Its a success, I dont need to look for any further passes of this loop. Lets end here by returning "SUCCESS"**
});
}
return future;
Upvotes: 1
Views: 941
Reputation: 298233
Just make chaining a retry action a part of the handler that is only executed when the success state is known. Since a function defined via lambda expression can’t refer to itself, you need a method which you can call, e.g.
static CompletableFuture<String> yourMethod() {
return yourMethod(1, getRetrialCountBasedOnSomeLogic());
}
private static CompletableFuture<String> yourMethod(int currentTry, int maxTries) {
CompletableFuture<String> future = doSomeCalculations(currentTry);
int nextTry = currentTry + 1;
return future
.handle((s, t) -> t == null? CompletableFuture.completedFuture("SUCCESS"):
nextTry <= maxTries? yourMethod(nextTry, maxTries):
CompletableFuture.completedFuture(
CompletableFutures.unwrapCompletionStateException(t).getMessage()))
.thenCompose(Function.identity());
}
Unfortunately, there is no variant of handle
mapping to another future, so we need to map to a future of future followed by .thenCompose(Function.identity())
You could reduce the work slightly for the last attempt, which doesn’t need composition, by conditionally chaining different stages:
private static CompletableFuture<String> yourMethod(int currentTry, int maxTries) {
CompletableFuture<String> future
= doSomeCalculations(currentTry).thenApply(x -> "SUCCESS");
int nextTry = currentTry + 1;
return nextTry <= maxTries?
future.thenApply(CompletableFuture::completedFuture)
.exceptionally(t -> yourMethod(nextTry, maxTries))
.thenCompose(Function.identity()):
future.exceptionally(t ->
CompletableFutures.unwrapCompletionStateException(t).getMessage());
}
This variant also works if maxTries
is not a constant but requires reevaluating getRetrialCountBasedOnSomeLogic()
, as your loop does:
static CompletableFuture<String> yourMethod() {
return yourMethod(1);
}
static CompletableFuture<String> yourMethod(int currentTry) {
int maxTries = getRetrialCountBasedOnSomeLogic();
CompletableFuture<String> future
= doSomeCalculations(currentTry).thenApply(x -> "SUCCESS");
int nextTry = currentTry + 1;
return nextTry <= maxTries?
future.thenApply(CompletableFuture::completedFuture)
.exceptionally(t -> yourMethod(nextTry))
.thenCompose(Function.identity()):
future.exceptionally(t ->
CompletableFutures.unwrapCompletionStateException(t).getMessage());
}
Upvotes: 2