2016-06-12 17 views
6

Jestem przyzwyczajony do wzoru ListenableFuture, z wywołami zwrotnymi onSuccess() i onFailure(), np.Czy procedura obsługi wyjątku przekazana do klasy CompletableFuture.exceptionally() musi zwrócić znaczącą wartość?

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

Wygląda na CompletableFuture Java 8 jest przeznaczona do obsługi mniej więcej ten sam przypadek użycia. Naiwnie, mogę zacząć tłumaczyć powyższy przykład jako:

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

Jest to z pewnością mniej gadatliwy niż wersja ListenableFuture i wygląda bardzo obiecująco.

Jednak to nie skompilować, ponieważ exceptionally() nie bierze Consumer<Throwable>, to zajmuje Function<Throwable, ? extends T> - w tym przypadku Function<Throwable, ? extends String>.

Oznacza to, że nie można po prostu zalogować się błąd, muszę wymyślić wartości String do zwrotu w przypadku błędu, i nie ma sensu wartość String, aby powrócić w przypadku błędu. Mogę wrócić null, tak aby uzyskać kod do kompilacji:

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

Ale to zaczyna się rozwlekły ponownie, a poza gadatliwości, nie podoba mi się, że mając null pływających wokół - to sugeruje, że ktoś spróbuj odzyskać lub przechwycić tę wartość, i że w pewnym momencie znacznie później mogę mieć nieoczekiwany NullPointerException.

Jeśli exceptionally() wziął Function<Throwable, Supplier<T>> mogłem przynajmniej zrobić coś takiego -

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

- ale tak nie jest.

Co należy zrobić, gdy exceptionally() nigdy nie powinno dawać prawidłowej wartości? Czy jest coś jeszcze, co mogę zrobić z CompletableFuture lub czymś innym w nowych bibliotekach Java 8, które lepiej obsługuje ten przypadek użycia?

Odpowiedz

7

Prawidłowa odpowiedź jest transformacja z CompletableFuture:

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

Innym sposobem:

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

Co ciekawe jest to, że zostałeś łańcuchowym przyszłość w swoich przykładach. Pozornie płynna składnia faktycznie przykuwa przyszłość, ale wydaje się, że tego tutaj nie chcesz.

Przyszłość zwrócona przez whenComplete może być interesująca, jeśli chcesz zwrócić przyszłość, która przetwarza coś z wynikiem wewnętrznej przyszłości. Zachowuje on obecny wyjątek przyszłości, jeśli taki istnieje. Jeśli jednak przyszłość zostanie zakończona normalnie, a kontynuacja zostanie rzucona, zakończy się ona wyjątkowo wraz z wyrzuconym wyjątkiem.

Różnica polega na tym, że wszystko, co wydarzy się po future, stanie się przed następną kontynuacją. Używanie exceptionally i jest równoważne, jeśli jesteś użytkownikiem końcowym future, ale jeśli odsyłasz rozmówcę do przyszłości, jedno z nich będzie przetwarzane bez powiadomienia o zakończeniu (tak jakby w tle, jeśli możesz) , prawdopodobnie kontynuacja, ponieważ prawdopodobnie będziesz chciał wyjątku do kaskady w dalszych ciągach.

+0

Pierwsza właściwie nie kompiluje, niestety, ponieważ 'log.error (...)' jest 'void' i' wyjątkowo() 'chce czegoś, co zwraca przyszły typ powrotu (w tym przypadku' String'). (Zasadniczo to doprowadziło do mojego pytania.) Dzięki za komentarze na temat płynnego API i łączenia. –

+0

Masz rację, odpowiednio zaktualizowałem. – acelent

2

Pamiętaj, że exceptionally(Function<Throwable,? extends T> fn) również zwraca CompletableFuture<T>. Więc możesz połączyć łańcuch.

Wartość zwracana Function<Throwable,? extends T> ma na celu wygenerowanie wyniku zastępczego dla następnych łańcuchowych metod. Możesz więc np. Pobrać wartość z pamięci podręcznej, jeśli jest ona niedostępna z 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); 

Jeśli exceptionally przyjąłby Supplier<T> zamiast funkcji, to jak to może zwrócić CompletableFuture<String> łańcuchowym dodatkowych wojsk?

Myślę, że chcesz wariant exceptionally, który zwróci void. Ale niestety, nie, nie ma takiego wariantu.

Tak więc, w twoim przypadku możesz bezpiecznie zwrócić jakąkolwiek wartość z tej funkcji rezerwowej, jeśli nie zwrócisz tego obiektu future i nie użyjesz go dalej w swoim kodzie (aby nie mógł być dalej przykuty). Lepiej nawet nie przypisać go do zmiennej.

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