简体   繁体   中英

How do you access completed futures passed to CompletableFuture allOf?

I am trying to get a grip of Java 8 CompletableFuture. How can I join these to person and return them after "allOf". The code under is not working but gives you an idea of what I have tried.

In javascript ES6 i would do

Promise.all([p1, p2]).then(function(persons) {
   console.log(persons[0]); // p1 return value     
   console.log(persons[1]); // p2 return value     
});

My efforts in Java so far

public class Person {

        private final String name;

        public Person(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

    }

@Test
public void combinePersons() throws ExecutionException, InterruptedException {
    CompletableFuture<Person> p1 = CompletableFuture.supplyAsync(() -> {
        return new Person("p1");
    });

    CompletableFuture<Person> p2 = CompletableFuture.supplyAsync(() -> {
        return new Person("p1");
    });

    CompletableFuture.allOf(p1, p2).thenAccept(it -> System.out.println(it));

}

The CompletableFuture#allOf method does not expose the collection of completed CompletableFuture instances that were passed to it.

Returns a new CompletableFuture that is completed when all of the given CompletableFuture s complete. If any of the given CompletableFuture s complete exceptionally, then the returned CompletableFuture also does so, with a CompletionException holding this exception as its cause. Otherwise, the results, if any, of the given CompletableFuture s are not reflected in the returned CompletableFuture , but may be obtained by inspecting them individually . If no CompletableFuture s are provided, returns a CompletableFuture completed with the value null .

Note that allOf also considers futures that were completed exceptionally as completed. So you won't always have a Person to work with. You might actually have an exception/throwable.

If you know the amount of CompletableFuture s you're working with, use them directly

CompletableFuture.allOf(p1, p2).thenAccept(it -> {
    Person person1 = p1.join();
    Person person2 = p2.join();
});

If you don't know how many you have (you're working with an array or list), just capture the array you pass to allOf

// make sure not to change the contents of this array
CompletableFuture<Person>[] persons = new CompletableFuture[] { p1, p2 };
CompletableFuture.allOf(persons).thenAccept(ignore -> {
   for (int i = 0; i < persons.length; i++ ) {
       Person current = persons[i].join();
   }
});

If you wanted your combinePersons method (ignoring it's a @Test for now) to return a Person[] containing all the Person objects from the completed futures, you could do

@Test
public Person[] combinePersons() throws Exception {
    CompletableFuture<Person> p1 = CompletableFuture.supplyAsync(() -> {
        return new Person("p1");
    });

    CompletableFuture<Person> p2 = CompletableFuture.supplyAsync(() -> {
        return new Person("p1");
    });

    // make sure not to change the contents of this array
    CompletableFuture<Person>[] persons = new CompletableFuture[] { p1, p2 };
    // this will throw an exception if any of the futures complete exceptionally
    CompletableFuture.allOf(persons).join();

    return Arrays.stream(persons).map(CompletableFuture::join).toArray(Person[]::new);
}

As pointed out in @Sotirios Delimanolis's answer, the CompletableFuture is not exception friendly, which means we cannot get the result of all futures easily when exceptions happen in one or several of the futures.

As OP's question is not limited to the successful situation, in this answer, I want to make an addition to this imperfect of CompletableFuture.

If we however want to know the results of all the futures, we can handle this separately when defining futures. For example:

    CompletableFuture<Person> p1 = CompletableFuture.supplyAsync(() -> {
      return new Person("p1");
    });
    
    p1.thenAccept(person -> {
      // handle successful future
      // for example:  add p1 to success operation list 
    });

    p1.exceptionally((exception) -> {
      // handle fail future
      // for example, log the parameter (not exist in this case) to create the person
      System.out.println("Exception happen in creating p1");
      return null;
    });

and then after calling CompletableFuture.allOf(persons).join() (Please also pay attention to the exception handling here), we can figure out which future succeeds and which future fails.

Hope this simple tip can help newcomers write solid code in real-world business systems.

CompletableFuture.allOf(p1, p2); // .get can be ignored
List<Person> res =
            List.of(p1, p2)  // list of futures
            .stream()
            .map(future -> {
               System.out.println("future " + future.join());
               return future.join();
            })  
            .collect(Collectors.toList());

Or you can fetch the values individually, using p1.get() and p2.get()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM