Search code examples
javaasynchronousconcurrencycompletable-future

Refactoring: Converting future.get() to CompletionStage.whenComplete()?


I currently have some code in Java which looks something like this, but I want to refactor this.

List<Object> combinedStatus = new ArrayList();
for (List<Long> batch: batches){
    CompletableFuture<Object> future = ask(batch);
    futureHolder.add(future);
}

try {
   for (CompletableFuture<Object> future: futureHolder) { 
      response = future.get();
      combinedStatus.add(response.result());
   } catch (Exception e) {
       // log exception 
   }

//return combinedStatus;

I see one major issue is that future.get() is blocking the thread. I want to refactor this to make it non blocking. Here is my refactored version - can someone give me some advice on if this is correct / makes sense / or how I can make this better?

for (List<Long> batch: batches) {
   CompletionStage<Object> stage = ask(batch);
    stage.whenComplete(
        (resp, ex) -> {
            if (ex != null) {
             // log exception 
           } else { 
               combinedStatus.add(response.result());
           }
     });
}

//return combinedStatus;

Any suggestions / what is the idiomatic approach to refactoring this?


Solution

  • You can not avoid blocking for a method that is supposed to return the final result, i.e. the List<Object>. The only way to make this method non-blocking, is by returning a future for the final result.

    Then, the most straight-forward way to deal with a list of futures, is to keep the loop, but perform it as a chained operation, when all futures are known to be completed, so querying the result won’t block.

    for(List<Long> batch: batches) {
        futureHolder.add(ask(batch));
    }
    
    return CompletableFuture.allOf(futureHolder.toArray(new CompletableFuture[0]))
        .thenApply(justVoid -> {
            List<Object> combinedStatus = new ArrayList<>();
            for(CompletableFuture<Object> future: futureHolder) {
                combinedStatus.add(future.join().result());
            }
            return combinedStatus;
        });
    

    The returned future will be completed exceptionally if any of the operation failed with an exception, so you don’t need to deal with exceptions within this code. The caller of you method can decide how to deal with exceptional results.