Solución:
Su código sugiere que está utilizando el resultado de la operación asincrónica más adelante en el mismo método, por lo que tendrá que lidiar con CompletionException
de todos modos, una forma de lidiar con eso es
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
}
Todas las excepciones lanzadas dentro del procesamiento asincrónico del Supplier
se envolverá en un CompletionException
al llamar join
, excepto el ServerException
ya hemos envuelto en un CompletionException
.
Cuando volvemos a lanzar la causa de la CompletionException
, podemos enfrentar excepciones no controladas, es decir, subclases de Error
o RuntimeException
, o nuestra excepción de verificación personalizada ServerException
. El código anterior los maneja a todos con una captura múltiple que los volverá a lanzar. Dado que el tipo de retorno declarado de getCause()
es Throwable
, el compilador requiere que manejemos ese tipo a pesar de que ya manejamos todos los tipos posibles. La solución sencilla es lanzar este imposible arrojadizo envuelto en un AssertionError
.
Alternativamente, podríamos usar un resultado futuro alternativo para nuestra excepción personalizada:
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
}
Esta solución volverá a lanzar todos los arrojables “inesperados” en su forma envuelta, pero solo arrojará los personalizados ServerException
en su forma original pasó a través del exception
futuro. Tenga en cuenta que tenemos que asegurarnos de que a
se ha completado (como llamar join()
primero), antes de consultar el exception
futuro, para evitar condiciones de carrera.
Para aquellos que buscan otras formas de manejo de excepciones con completableFuture
A continuación, se muestran varias formas, por ejemplo, de manejar el error de análisis a entero:
1. Utilizando handle
método – que le permite proporcionar un valor predeterminado en caso de excepción
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. Usando exceptionally
Método – Similar a handle
pero menos detallado
CompletableFuture parser = CompletableFuture.supplyAsync(() -> "1")
.thenApply(Integer::parseInt)
.exceptionally(t -> {
t.printStackTrace();
return 0;
}).thenAcceptAsync(s -> System.out.println("CORRECT value: " + s));
3. Usando whenComplete
Método – el uso de esto detendrá el método en sus pistas y no ejecutará el siguiente 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. Propagar la excepción a través de 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;
}
Creo que deberías envolver eso en un RuntimeException
y tira eso:
throw new RuntimeException(ex);
O muchos serían una pequeña utilidad que ayudaría:
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();
}
Y luego podrías unwrap
ese..
try {
go();
} catch (Wrapper w) {
throw w.unwrap();
}