J'ai le code suivant:
// How to throw the ServerException?
public void myFunc() throws ServerException{
// Some code
CompletableFuture<A> a = CompletableFuture.supplyAsync(() -> {
try {
return someObj.someFunc();
} catch(ServerException ex) {
// throw ex; gives an error here.
}
}));
// Some code
}
someFunc()
lance un ServerException
. Je ne veux pas gérer cela ici, mais lève l'exception de someFunc()
à l'appelant de myFunc()
.
Votre code suggère que vous utilisiez le résultat de l'opération asynchrone ultérieurement dans la même méthode. Vous devrez donc traiter avec CompletionException
de toute façon. Une façon de le gérer est donc:
public void myFunc() throws ServerException {
// Some code
CompletableFuture<A> a = CompletableFuture.supplyAsync(() -> {
try { return someObj.someFunc(); }
catch(ServerException ex) { throw new CompletionException(ex); }
});
// Some code running in parallel to someFunc()
A resultOfA;
try {
resultOfA = a.join();
}
catch(CompletionException ex) {
try {
throw ex.getCause();
}
catch(Error|RuntimeException|ServerException possible) {
throw possible;
}
catch(Throwable impossible) {
throw new AssertionError(impossible);
}
}
// some code using resultOfA
}
Toutes les exceptions lancées dans le traitement asynchrone du Supplier
seront englobées dans un CompletionException
lors de l'appel de join
, à l'exception du ServerException
que nous avons déjà encapsulé dans un CompletionException
.
Lorsque nous relançons la cause du CompletionException
, nous pouvons être confrontés à des exceptions non contrôlées, c.-à-d. Des sous-classes de Error
ou RuntimeException
, ou à notre exception personnalisée contrôlée ServerException
. Le code ci-dessus les gère tous avec une capture multiple qui les relancera. Comme le type de retour déclaré de getCause()
est Throwable
, le compilateur nous demande de gérer ce type, même si nous avons déjà traité tous les types possibles. La solution simple consiste à jeter ce jetable réellement impossible dans un AssertionError
.
Alternativement, nous pourrions utiliser un résultat alternatif futur pour notre exception personnalisée:
public void myFunc() throws ServerException {
// Some code
CompletableFuture<ServerException> exception = new CompletableFuture<>();
CompletableFuture<A> a = CompletableFuture.supplyAsync(() -> {
try { return someObj.someFunc(); }
catch(ServerException ex) {
exception.complete(ex);
throw new CompletionException(ex);
}
});
// Some code running in parallel to someFunc()
A resultOfA;
try {
resultOfA = a.join();
}
catch(CompletionException ex) {
if(exception.isDone()) throw exception.join();
throw ex;
}
// some code using resultOfA
}
Cette solution relance tous les objets jetables "inattendus" dans leur forme enveloppée, mais ne lance que la commande personnalisée ServerException
dans sa forme originale transmise via le futur exception
. Notez que nous devons nous assurer que a
est terminé (comme l'appelant join()
en premier), avant d'interroger le futur exception
, afin d'éviter les conditions de concurrence critique.
Pour ceux qui recherchent d'autres manières de gérer les exceptions avec completableFuture
Vous trouverez ci-dessous plusieurs manières de gérer, par exemple, Erreur d'analyse en entier:
1. En utilisant la méthode handle
- qui vous permet de fournir une valeur par défaut à l'exception
CompletableFuture correctHandler = CompletableFuture.supplyAsync(() -> "A")
.thenApply(Integer::parseInt)
.handle((result, ex) -> {
if (null != ex) {
ex.printStackTrace();
return 0;
} else {
System.out.println("HANDLING " + result);
return result;
}
})
.thenAcceptAsync(s -> {
System.out.println("CORRECT: " + s);
});
2. Utiliser exceptionally
Method - semblable à handle
mais moins verbeux
CompletableFuture parser = CompletableFuture.supplyAsync(() -> "1")
.thenApply(Integer::parseInt)
.exceptionally(t -> {
t.printStackTrace();
return 0;
}).thenAcceptAsync(s -> System.out.println("CORRECT value: " + s));
. Utiliser whenComplete
Method - utiliser ceci arrêtera la méthode sur ses pistes et n'exécutera pas le prochain thenAcceptAsync
CompletableFuture correctHandler2 = CompletableFuture.supplyAsync(() -> "A")
.thenApply(Integer::parseInt)
.whenComplete((result, ex) -> {
if (null != ex) {
ex.printStackTrace();
}
})
.thenAcceptAsync(s -> {
System.out.println("When Complete: " + s);
});
4. Propagation de l'exception via completeExceptionally
public static CompletableFuture<Integer> converter(String convertMe) {
CompletableFuture<Integer> future = new CompletableFuture<>();
try {
future.complete(Integer.parseInt(convertMe));
} catch (Exception ex) {
future.completeExceptionally(ex);
}
return future;
}
Je pense que vous devriez envelopper cela dans un RuntimeException
et le lancer:
throw new RuntimeException(ex);
Ou beaucoup être un petit utilitaire aiderait:
static class Wrapper extends RuntimeException {
private Wrapper(Throwable throwable) {
super(throwable);
}
public static Wrapper wrap(Throwable throwable) {
return new Wrapper(throwable);
}
public Throwable unwrap() {
return getCause();
}
}
public static void go() {
CompletableFuture<String> a = CompletableFuture.supplyAsync(() -> {
try {
throw new Exception("Just because");
} catch (Exception ex) {
throw Wrapper.wrap(ex);
}
});
a.join();
}
Et alors vous pourriez unwrap
ça ..
try {
go();
} catch (Wrapper w) {
throw w.unwrap();
}
Même si la réponse de l'autre est très gentille. mais je vous donne un autre moyen de lancer une exception vérifiée dans CompletableFuture
.
SI vous ne voulez pas invoquer un CompletableFuture
dans un autre thread, vous pouvez utiliser une classe anonyme pour le gérer comme ceci:
CompletableFuture<A> a = new CompletableFuture<A>() {{
try {
complete(someObj.someFunc());
} catch (ServerException ex) {
completeExceptionally(ex);
}
}};
SI vous voulez invoquer un CompletableFuture
dans un autre thread, vous pouvez également utiliser une classe anonyme pour le gérer, mais exécuter la méthode par runAsync
:
CompletableFuture<A> a = new CompletableFuture<A>() {{
CompletableFuture.runAsync(() -> {
try {
complete(someObj.someFunc());
} catch (ServerException ex) {
completeExceptionally(ex);
}
});
}};