J'essaie d'appeler une API de repos pour la demande PUT
dans une boucle. Chaque appel est un CompletableFuture
. Chaque appel api renvoie un objet de type RoomTypes.RoomType
Je souhaite collecter les réponses (à la fois les réponses réussies et les réponses d'erreur) dans différentes listes. Comment puis-je y parvenir? Je suis sûr que je ne peux pas utiliser allOf
car il n'obtiendrait pas tous les résultats si un appel ne parvient pas à se mettre à jour.
Comment enregistrer les erreurs/exceptions pour chaque appel?
public void sendRequestsAsync(Map<Integer, List> map1) {
List<CompletableFuture<Void>> completableFutures = new ArrayList<>(); //List to hold all the completable futures
List<RoomTypes.RoomType> responses = new ArrayList<>(); //List for responses
ExecutorService yourOwnExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
for (Map.Entry<Integer, List> entry :map1.entrySet()) {
CompletableFuture requestCompletableFuture = CompletableFuture
.supplyAsync(
() ->
//API call which returns object of type RoomTypes.RoomType
updateService.updateRoom(51,33,759,entry.getKey(),
new RoomTypes.RoomType(entry.getKey(),map2.get(entry.getKey()),
entry.getValue())),
yourOwnExecutor
)//Supply the task you wanna run, in your case http request
.thenApply(responses::add);
completableFutures.add(requestCompletableFuture);
}
Vous pouvez simplement utiliser allOf()
pour obtenir un avenir qui est terminé lorsque tous vos futurs initiaux sont terminés (exceptionnellement ou non), puis les diviser entre succès et échec en utilisant Collectors.partitioningBy()
:
List<CompletableFuture<RoomTypes.RoomType>> completableFutures = new ArrayList<>(); //List to hold all the completable futures
ExecutorService yourOwnExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
for (Map.Entry<Integer, List> entry : map1.entrySet()) {
CompletableFuture<RoomTypes.RoomType> requestCompletableFuture = CompletableFuture
.supplyAsync(
() ->
//API call which returns object of type RoomTypes.RoomType
updateService.updateRoom(51, 33, 759, entry.getKey(),
new RoomTypes.RoomType(entry.getKey(), map2.get(entry.getKey()),
entry.getValue())),
yourOwnExecutor
);
completableFutures.add(requestCompletableFuture);
}
CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[0]))
// avoid throwing an exception in the join() call
.exceptionally(ex -> null)
.join();
Map<Boolean, List<CompletableFuture<RoomTypes.RoomType>>> result =
completableFutures.stream()
.collect(Collectors.partitioningBy(CompletableFuture::isCompletedExceptionally)));
La carte résultante contiendra une entrée avec true
pour les futures échoués, et une autre entrée avec false
clé pour les réussites. Vous pouvez ensuite inspecter les 2 entrées pour agir en conséquence.
Notez qu'il y a 2 légères modifications par rapport à votre code d'origine:
requestCompletableFuture
est maintenant un CompletableFuture<RoomTypes.RoomType>
thenApply(responses::add)
et la liste de responses
ont été suppriméesConcernant la journalisation/la gestion des exceptions, ajoutez simplement la requestCompletableFuture.handle()
appropriée pour les enregistrer individuellement, mais conservez la requestCompletableFuture
et non celle résultant de handle()
.
Alternativement, vous pouvez peut-être aborder le problème sous un angle différent et au lieu de forcer l'utilisation de CompletableFuture
, vous pouvez utiliser un CompletionService à la place.
L'idée générale du CompletionService
est que dès qu'une réponse pour un futur donné est prête, elle est placée dans une file d'attente à partir de laquelle vous pouvez consommer les résultats.
Alternative 1: Sans CompletableFuture
CompletionService<String> cs = new ExecutorCompletionService<>(executor);
List<Future<String>> futures = new ArrayList<>();
futures.add(cs.submit(() -> "One"));
futures.add(cs.submit(() -> "Two"));
futures.add(cs.submit(() -> "Three"));
futures.add(cs.submit(() -> { throw new RuntimeException("Sucks to be four"); }));
futures.add(cs.submit(() -> "Five"));
List<String> successes = new ArrayList<>();
List<String> failures = new ArrayList<>();
while (futures.size() > 0) {
Future<String> f = cs.poll();
if (f != null) {
futures.remove(f);
try {
//at this point the future is guaranteed to be solved
//so there won't be any blocking here
String value = f.get();
successes.add(value);
} catch (Exception e) {
failures.add(e.getMessage());
}
}
}
System.out.println(successes);
System.out.println(failures);
Ce qui donne:
[One, Two, Three, Five]
[Java.lang.RuntimeException: Sucks to be four]
Alternative 2: Avec CompletableFuture
Cependant, si vous avez vraiment, vraiment besoin de gérer CompletableFuture
, vous pouvez également les soumettre au service d'achèvement, simplement en les plaçant directement dans sa file d'attente:
Par exemple, la variation suivante a le même résultat:
BlockingQueue<Future<String>> tasks = new ArrayBlockingQueue<>(5);
CompletionService<String> cs = new ExecutorCompletionService<>(executor, tasks);
List<Future<String>> futures = new ArrayList<>();
futures.add(CompletableFuture.supplyAsync(() -> "One"));
futures.add(CompletableFuture.supplyAsync(() -> "Two"));
futures.add(CompletableFuture.supplyAsync(() -> "Three"));
futures.add(CompletableFuture.supplyAsync(() -> { throw new RuntimeException("Sucks to be four"); }));
futures.add(cs.submit(() -> "Five"));
//places all futures in completion service queue
tasks.addAll(futures);
List<String> successes = new ArrayList<>();
List<String> failures = new ArrayList<>();
while (futures.size() > 0) {
Future<String> f = cs.poll();
if (f != null) {
futures.remove(f);
try {
//at this point the future is guaranteed to be solved
//so there won't be any blocking here
String value = f.get();
successes.add(value);
} catch (Exception e) {
failures.add(e.getMessage());
}
}
}