web-dev-qa-db-fra.com

Exception de projection de CompletableFuture

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().

22
ayushgp

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.

34
Holger

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;
    }
17
mel3kings

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();
 }
3
Eugene

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);
        }
    });
}};
2
holi-java