Yash Khare
Yash Khare

Reputation: 375

Stop/Complete a looped CompletableFuture based on some condition

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

Answers (1)

Holger
Holger

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

Related Questions