2016-06-12 14 views
6

Sono abituato allo schema ListenableFuture, con le chiamate onSuccess() e onFailure(), ad es.Un gestore di eccezioni è passato a CompletableFuture.exceptionally() deve restituire un valore significativo?

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); 
    } 
}) 

Sembra Java 8 di CompletableFuture ha lo scopo di gestire più o meno lo stesso caso d'uso. Ingenuamente, ho potuto iniziare a tradurre l'esempio precedente come:

CompletableFuture<String> future = CompletableFuture<String>.supplyAsync(...) 
    .thenAccept(this::handleResult) 
    .exceptionally((t) -> log.error("Unexpected error", t)); 

Questo è certamente meno prolisso rispetto alla versione ListenableFuture e sembra molto promettente.

Tuttavia, non si compila, perché exceptionally() non ci vuole un Consumer<Throwable>, ci vuole un Function<Throwable, ? extends T> - in questo caso, un Function<Throwable, ? extends String>.

Ciò significa che non posso semplicemente registrare l'errore, devo trovare un valore String da restituire nel caso dell'errore, e non c'è un valore significativo String da restituire nel caso dell'errore. Posso tornare null, solo per ottenere il codice per compilare:

.exceptionally((t) -> { 
    log.error("Unexpected error", t); 
    return null; // hope this is ignored 
    }); 

Ma questo sta iniziando a farsi verbose di nuovo, e al di là di dettaglio, non mi piace avere quel null galleggia intorno - suggerisce che qualcuno potrebbe prova a recuperare o catturare quel valore, e che a un certo punto molto più tardi potrei avere un inaspettato NullPointerException.

Se exceptionally() preso un Function<Throwable, Supplier<T>> ho potuto almeno fare qualcosa di simile -

.exceptionally((t) -> { 
    log.error("Unexpected error", t); 
    return() -> { 
     throw new IllegalStateException("why are you invoking this?"); 
    } 
    }); 

- ma non è così.

Qual è la cosa giusta da fare quando exceptionally() non dovrebbe mai produrre un valore valido? C'è qualcos'altro che posso fare con CompletableFuture, o qualcos'altro nelle nuove librerie Java 8, che meglio supporta questo caso d'uso?

risposta

7

Una corretta trasformazione corrispondente con CompletableFuture è:

CompletableFuture<String> future = CompletableFuture.supplyAsync(...); 
future.thenAccept(this::handleResult); 
future.exceptionally(t -> { 
    log.error("Unexpected error", t); 
    return null; 
}); 

Un altro modo:

CompletableFuture<String> future = CompletableFuture.supplyAsync(...); 
future 
    .whenComplete((r, t) -> { 
     if (t != null) { 
      log.error("Unexpected error", t); 
     } 
     else { 
      this.handleResult(r); 
     } 
    }); 

La parte interessante è che sono stati i futures concatenano nei vostri esempi. La sintassi apparentemente fluente sta effettivamente incatenando i futuri, ma sembra che non lo vogliate qui.

Il futuro restituito da whenComplete potrebbe essere interessante se si desidera restituire un futuro che elabora qualcosa con un risultato interno futuro. Conserva l'eccezione del futuro corrente, se presente. Tuttavia, se il futuro si completasse normalmente e il seguito continuasse, si completerà eccezionalmente con l'eccezione generata.

La differenza è che tutto ciò che accade dopo il completamento di avverrà prima della prosecuzione successiva. L'utilizzo di exceptionally e thenAccept equivale se sei l'utente finale di future, ma se stai restituendo un futuro a un chiamante, uno dei due processerà senza una notifica di completamento (come se fosse in background, se puoi) , molto probabilmente la continuazione exceptionally poiché probabilmente vorrai che l'eccezione si sovrapponga a ulteriori continuazioni.

+0

Il primo in realtà non viene compilato, sfortunatamente, perché 'log.error (...)' è 'void' e' eccezionalmente() 'vuole qualcosa che restituisce il tipo di ritorno del futuro (in questo caso un' String'). (Che è fondamentalmente ciò che ha portato alla mia domanda.) Grazie per i commenti sull'API fluente e concatenamento, però. –

+0

Hai ragione, mi sono aggiornato di conseguenza. – acelent

2

Nota, che exceptionally(Function<Throwable,? extends T> fn) restituisce anche CompletableFuture<T>. Quindi puoi continuare a catena.

Il valore di ritorno di Function<Throwable,? extends T> ha lo scopo di produrre un risultato di fallback per i metodi concatenati. Quindi è possibile ad esempio ottenere il valore da Cache se non è disponibile dal DB.

CompletableFuture<String> future = CompletableFuture<String>.supplyAsync(/*get from DB*/) 
    .exceptionally((t) -> { 
    log.error("Unexpected error", t); 
    return "Fallback value from cache"; 
    }) 
    .thenAccept(this::handleResult); 

Se exceptionally accetterebbero Supplier<T> invece della funzione, allora come potrebbe restituire un CompletableFuture<String> per il concatenamento futher?

Penso che si desidera una variante di exceptionally che restituirebbe void. Ma sfortunatamente no, non esiste una tale variante.

Quindi, nel tuo caso puoi tranquillamente restituire qualsiasi valore da questa funzione di fallback, se non restituisci questo oggetto future e non utilizzarlo più nel codice (quindi non può essere incatenato ulteriormente). Meglio nemmeno assegnarlo a una variabile.

CompletableFuture<String>.supplyAsync(/*get from DB*/) 
    .thenAccept(this::handleResult) 
    .exceptionally((t) -> { 
    log.error("Unexpected error", t); 
    return null; 
    }); 
Problemi correlati