Je suis habitué au modèle ListenableFuture
, avec les rappels onSuccess()
et onFailure()
, par exemple.
ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
ListenableFuture<String> future = service.submit(...)
Futures.addCallback(future, new FutureCallback<String>() {
public void onSuccess(String result) {
handleResult(result);
}
public void onFailure(Throwable t) {
log.error("Unexpected error", t);
}
})
Il semble que Java 8's CompletableFuture
est destiné à gérer plus ou moins le même cas d'utilisation. Naïvement, je pourrais commencer à traduire l'exemple ci-dessus comme :
CompletableFuture<String> future = CompletableFuture<String>.supplyAsync(...)
.thenAccept(this::handleResult)
.exceptionally((t) -> log.error("Unexpected error", t));
C'est certainement moins verbeux que la version ListenableFuture
et semble très prometteur.
Cependant, il ne compile pas, car exceptionally()
ne prend pas de Consumer<Throwable>
, Il prend un Function<Throwable, ? extends T>
- dans ce cas, un Function<Throwable, ? extends String>
.
Cela signifie que je ne peux pas simplement enregistrer l'erreur, je dois trouver une valeur String
à retourner dans le cas d'erreur, et il n'y a pas de valeur String
significative à renvoyer dans l'erreur Cas. Je peux retourner null
, juste pour obtenir le code à compiler:
.exceptionally((t) -> {
log.error("Unexpected error", t);
return null; // hope this is ignored
});
Mais cela commence à devenir verbeux à nouveau, et au-delà de la verbosité, je n'aime pas avoir ce null
flottant - cela suggère que quelqu'un pourrait essayer de récupérer ou de capturer cette valeur, et cela à un moment donné beaucoup plus tard Je pourrais avoir un NullPointerException
inattendu.
Si exceptionally()
prenait un Function<Throwable, Supplier<T>>
Je pourrais au moins faire quelque chose comme ça -
.exceptionally((t) -> {
log.error("Unexpected error", t);
return () -> {
throw new IllegalStateException("why are you invoking this?");
}
});
- mais ce n'est pas le cas.
Quelle est la bonne chose à faire lorsque exceptionally()
ne doit jamais produire une valeur valide? Y a-t-il autre chose que je peux faire avec CompletableFuture
, ou autre chose dans les nouvelles bibliothèques Java 8, qui prend mieux en charge ce cas d'utilisation?
Une transformation correspondante correcte avec CompletableFuture
est:
CompletableFuture<String> future = CompletableFuture.supplyAsync(...);
future.thenAccept(this::handleResult);
future.exceptionally(t -> {
log.error("Unexpected error", t);
return null;
});
Autrement:
CompletableFuture<String> future = CompletableFuture.supplyAsync(...);
future
.whenComplete((r, t) -> {
if (t != null) {
log.error("Unexpected error", t);
}
else {
this.handleResult(r);
}
});
La partie intéressante ici est que vous enchaîniez les futurs dans vos exemples. La syntaxe apparemment fluide enchaîne en fait les futurs, mais il semble que vous ne vouliez pas cela ici.
L'avenir renvoyé par whenComplete
peut être intéressant si vous souhaitez renvoyer un avenir qui traite quelque chose avec le résultat d'un avenir interne. Il préserve l'exception de l'avenir actuel, le cas échéant. Cependant, si l'avenir se termine normalement et que la suite se lance, il se terminera exceptionnellement avec l'exception levée.
La différence est que tout ce qui se passe après la fin de future
se produira avant la prochaine continuation. L'utilisation de exceptionally
et thenAccept
est équivalente si vous êtes l'utilisateur final de future
, mais si vous fournissez un futur à un appelant, l'un ou l'autre sera traité sans notification d'achèvement (comme si vous étiez en arrière-plan, si vous le pouvez), très probablement la continuation exceptionally
puisque vous voudrez probablement que l'exception se répercute en cascade sur les autres continuations.
Notez que exceptionally(Function<Throwable,? extends T> fn)
renvoie également CompletableFuture<T>
. Vous pouvez donc enchaîner plus.
La valeur de retour de Function<Throwable,? extends T>
est destiné à produire un résultat de secours pour les méthodes enchaînées suivantes. Ainsi, vous pouvez par exemple obtenir la valeur du cache si elle n'est pas disponible dans la base de données.
CompletableFuture<String> future = CompletableFuture<String>.supplyAsync(/*get from DB*/)
.exceptionally((t) -> {
log.error("Unexpected error", t);
return "Fallback value from cache";
})
.thenAccept(this::handleResult);
Si exceptionally
accepterait Supplier<T>
au lieu de fonction, alors comment cela pourrait renvoyer un CompletableFuture<String>
pour enchaîner plus loin?
Je pense que vous voulez une variante de exceptionally
qui retournerait void
. Mais malheureusement, non, il n'y a pas une telle variante.
Donc, dans votre cas, vous pouvez retourner en toute sécurité n'importe quelle valeur de cette fonction de secours, si vous ne retournez pas cet objet future
et ne l'utilisez pas plus loin dans votre code (il ne peut donc pas être chaîné plus loin). Mieux vaut ne pas l'affecter à une variable.
CompletableFuture<String>.supplyAsync(/*get from DB*/)
.thenAccept(this::handleResult)
.exceptionally((t) -> {
log.error("Unexpected error", t);
return null;
});