web-dev-qa-db-ja.com

CompletableFuture.exceptionally()に渡された例外ハンドラーは、意味のある値を返す必要がありますか?

私は ListenableFuture パターンに慣れており、onSuccess()およびonFailure()コールバックを使用します。

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

Java 8's CompletableFuture は多かれ少なかれ同じユースケースを処理するためのものです。単純に、上の例を次のように翻訳し始めることができます:

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

これは確かにListenableFutureバージョンより冗長ではなく、非常に有望に見えます。

ただし、exceptionally()は_Consumer<Throwable>_を使用しないため、コンパイルされません。_Function<Throwable, ? extends T>_(この場合は_Function<Throwable, ? extends String>_)を使用します。

これは、エラーをログに記録するだけではなく、エラーの場合に返すにはString値を考え出す必要があり、エラーで返す意味のあるString値がないことを意味します場合。コードをコンパイルするためだけにnullを返すことができます。

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

しかし、これは再び冗長になり始めており、冗長性を超えて、そのnullが浮かんでいるのは好きではありません-誰かがその値を取得またはキャプチャしようとする可能性があり、かなり後で予期しないNullPointerExceptionが発生する可能性があります。

exceptionally()が_Function<Throwable, Supplier<T>>_を取った場合、少なくとも次のようなことができます-

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

-しかし、そうではありません。

exceptionally()が有効な値を生成してはならない場合に正しいことは何ですか?私がCompletableFutureでできる他のことはありますか、またはこのJava 8ライブラリでは、この使用例をより適切にサポートする他の何かですか?

17
David Moles

CompletableFutureを使用した対応する正しい変換は次のとおりです。

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

別の方法:

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

ここで興味深いのは、例の中で先物を連鎖させていたことです。一見流暢に見える構文は実際には先物を連鎖させていますが、ここではそれを望まないようです。

whenCompleteによって返されるフューチャーは、内部フューチャーの結果で何かを処理するフューチャーを返したい場合に興味深いかもしれません。現在の未来の例外があればそれを保存します。ただし、futureが正常に完了し、継続がスローされた場合、スローされた例外で例外が完了します。

違いは、futureの完了後に発生することはすべて、次の継続の前に発生することです。 exceptionallythenAcceptの使用は、futureのエンドユーザーの場合は同等ですが、呼び出し元にフューチャーを提供している場合は、どちらも完了通知(バックグラウンドにあるかのように)、おそらくexceptionallyの継続。これは、例外をさらに継続するためにカスケードする必要があるためです。

12
acelent

exceptionally(Function<Throwable,? extends T> fn)CompletableFuture<T>を返すことに注意してください。したがって、さらに連鎖させることができます。

Function<Throwable,? extends T>の戻り値は、次のチェーンメソッドのフォールバック結果を生成するためのものです。したがって、たとえば、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);

exceptionallyが関数の代わりにSupplier<T>を受け入れる場合、さらにチェーンするためにCompletableFuture<String>をどのように返すことができますか?

exceptionallyを返すvoidのバリアントが必要だと思います。しかし、残念ながら、そのような亜種はありません。

したがって、あなたのケースでは、このfutureオブジェクトを返さず、コードでそれ以上使用しない場合は、このフォールバック関数から任意の値を安全に返すことができます(そのため、さらにチェーンすることはできません)。それを変数に割り当てない方が良いでしょう。

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