How to simplify retry code block with java 8 features
What you can do is separate out the retry logic. You'll need some ancillary scaffolding:
interface ThrowingTask {
void run() throws ExecutionException;
}
Now, you write:
boolean runWithRetries(int maxRetries, ThrowingTask t) {
int count = 0;
while (count < maxRetries) {
try {
t.run();
return true;
}
catch (ExecutionException e) {
if (++count >= maxRetries)
return false;
}
}
}
Now, you can run things with retries without having to conflate your task logic with your retry logic:
runWithRetries(MAX_RETRIES, () -> { /* do stuff */ });
You can tweak this as you like to accept lambdas which are called on retry, return the retry count, etc etc. But the game is to write methods like runWithRetries
which capture the control flow but abstract over what behavior needs to be done -- so you only have to write your retry loop once, and then fill in the actual behavior you want wherever needed.
Well, more functional approach in my opinion will be to use Try
monad which unfortunately is not there for us in jdk 8 :(
Nevertheless you still can use better-monads library which provides it. Having that you can come up with some implementation like this:
public static <Out> Try<Out> tryTimes(int times, TrySupplier<Out> attempt) {
Supplier<Try<Out>> tryAttempt = () -> Try.ofFailable(attempt::get);
return IntStream.range(1, times)
.mapToObj(i -> tryAttempt)
.reduce(tryAttempt, (acc, current) -> () -> acc.get().recoverWith(error -> current.get()))
.get();
}
Long story short this function just chains calls of tryAttempt
and in case of failed attempt tries to recoverWith
the next call of tryAttempt
. Client code is going to look like this:
tryTimes(10, () -> {
// all the logic to do your possibly failing stuff
}
);
As a result client code is going to get Try<T>
which can be unpacked by direct call of .get()
(in case of success returns the value, in case of failure throws underlying exception) or with other methods described in library documentation.
Hope it helps.
UPDATE:
This can be done also in functional way using the filter
, findFirst
and limit
and without any external libraries:
interface ThrowingSupplier<Out> { Out supply() throws Exception; }
public static <Out> Optional<Out> tryTimes(int times, ThrowingSupplier<Out> attempt) {
Supplier<Optional<Out>> catchingSupplier = () -> {
try {
return Optional.ofNullable(attempt.supply());
} catch (Exception e) {
return Optional.empty();
}
};
return Stream.iterate(catchingSupplier, i -> i)
.limit(times)
.map(Supplier::get)
.filter(Optional::isPresent)
.findFirst()
.flatMap(Function.identity());
}
The client code remains the same. Also, please note that it is not going to evaluate expression times
times, but will stop on the first successful attempt.