CompletableFuture withFallback / обрабатывать только некоторые ошибки

Я получаю ответы на сервисный звонок через CompletableFuture. Я хотел бы обработать некоторые известные исключения, которые возвращает служба, например конфликты оптимистичного управления параллелизмом.

Вот что у меня есть. Есть ли лучший способ сделать это, который не переносит исключения или не использует SneakyThrows? Обертывание исключений означало бы, что другие обработчики исключений должны проверять причинные цепочки, а не просто использовать instanceof.

someService.call(request)
    .handle((response, error) -> {
        if (error == null)
            return CompletableFuture.completedFuture(response);
        else if (error instanceof OCCException)
            return CompletableFuture.completedFuture(makeDefaultResponse());

        CompletableFuture<Response> errorFuture = new CompletableFuture<>();
        errorFuture.completeExceptionally(error);
        return errorFuture;
    }).thenCompose(Function.identity());

В том же духе, есть ли способ реплицировать гуаву withFallback без обертывания-разворачивания?

CompletableFuture<T> withFallback(CompletableFuture<T> future,
                                  Function<Throwable, ? extends CompletableFuture<T>> fallback) {
    return future.handle((response, error) -> {
        if (error == null)
            return CompletableFuture.completedFuture(response);
        else
            return fallback.apply(error);
    }).thenCompose(Function.identity());
}


...
// Here's the first part of the question implemented using withFallback.
// It's a little cleaner, but it still requires wrapping as a future.
withFallback(someService.call(request), error -> {
    if (error instanceof OCCException)
        return CompletableFuture.completedFuture(makeDefaultResponse());

    CompletableFuture<Response> errorFuture = new CompletableFuture<>();
    errorFuture.completeExceptionally(error);
    return errorFuture;
});

Для полноты, вот как бы я разрешил обернуть исключения. (У меня есть модульный тест, чтобы убедиться, что выброшенное исключение распространяется по цепочке):

someService.call(request)
    .exceptionally(error -> {
        if (error instanceof OCCException)
            return makeDefaultResponse();
        else
            // wrap because error is declared as a checked exception
            throw new RuntimeException(error);
    });

person Michael Deardeuff    schedule 16.08.2014    source источник


Ответы (2)


Запрошенную вами функцию стиля гуавы можно реализовать следующим образом:

static <T> CompletableFuture<T> withFallback(CompletableFuture<T> future,
                  Function<Throwable, ? extends CompletableFuture<T>> fallback) {
  return future.handle((response, error) -> error)
    .thenCompose(error -> error!=null? fallback.apply(error): future);
}

будучи одновременно более компактным и экономящим ресурсы за счет повторного использования исходного кода future для случая, когда мы не хотим делать никаких преобразований. Но чтобы дать вызывающей стороне возможность сделать то же самое без введения другого вспомогательного метода, было бы очень полезно изменить метод и использовать BiFunction, который получает исходное будущее в качестве дополнительного параметра:

static <T> CompletableFuture<T> withFallback(CompletableFuture<T> future,
  BiFunction<CompletableFuture<T>, Throwable, ? extends CompletableFuture<T>>
                                                                      fallback) {
    return future.handle((response, error) -> error)
      .thenCompose(error -> error!=null? fallback.apply(future,error): future);
}

тогда вы можете использовать это так:

withFallback(someService.call(request), (f,t) -> t instanceof OCCException?
                  CompletableFuture.completedFuture(makeDefaultResponse()): f)
person Holger    schedule 18.08.2014
comment
Это прекрасно! Перенести ошибку в будущее - хорошая идея. - person Michael Deardeuff; 18.08.2014
comment
Потрясающий! Это спасло мне день. - person SwissArmyKnife; 14.09.2017

Единственный способ, который я могу придумать, - это определить вспомогательный метод, подобный этому:

static <T, E extends Throwable> CompletableFuture<T> betterHandle(CompletableFuture<T> source, Class<E> excClass, Supplier<T> sup) {
    CompletableFuture<T> result = new CompletableFuture<>();
    source.whenComplete( (t, ex) -> {
        if (ex == null) {
            result.complete(t);
        } else if (excClass.isInstance(ex)) {
            result.complete(sup.get());
        } else {
            result.completeExceptionally(ex);
        }
    });
    return result;
}

Это некрасиво, но позволит избежать обертывания исключений:

CompletableFuture<...> result = betterHandle(
    someService.call(request), 
    OCCException.class, 
    () -> makeDefaultResponse()
);
person Misha    schedule 16.08.2014