public final class AsyncTrampoline extends Object
When working with CompletionStage
, it's often desirable to have a loop like construct
which keeps producing stages until some condition is met. Because continuations are asynchronous,
it's usually easiest to do this with a recursive approach:
CompletionStage<Integer> getNextNumber();
CompletionStage<Integer> getFirstOddNumber(int current) {
if (current % 2 != 0)
// found odd number
return CompletableFuture.completedFuture(current);
else
// get the next number and recurse
return getNextNumber().thenCompose(next -> getFirstOddNumber(next));
}
The problem with this is that if the implementation of getNextNumber happens to be synchronous
CompletionStage<Integer> getNextNumber() {
return CompletableFuture.completedFuture(random.nextInt());
}
then getFirstOddNumber can easily cause a stack overflow. This situation often happens when a
cache is put under an async API, and all the values are cached and returned immediately. This
could be avoided by scheduling the recursive calls back to a thread pool using
CompletionStage.thenComposeAsync(java.util.function.Function<? super T, ? extends java.util.concurrent.CompletionStage<U>>)
, however the overhead of the thread pool submissions may
be high and may cause unnecessary context switching.
The methods on this class ensure that the stack doesn't blow up - if multiple calls happen on the same thread they are queued and run in a loop. You could write the previous example like this:
CompletionStage<Integer> getFirstOddNumber(int initial) {
return AsyncTrampoline.asyncWhile(
i -> i % 2 == 0,
i -> getNextNumber(),
initial);
}
Though this class provides efficient methods for a few loop patterns, many are better represented
by the more expressive API available on AsyncIterator
, which is also stack safe. For
example, the preceding snippet can be expressed as
AsyncIterator.generate(this::getNextNumber).find(i -> i % 2 != 0)
AsyncIterator
Modifier and Type | Method | Description |
---|---|---|
static <T> CompletionStage<T> |
asyncDoWhile(Function<? super T,? extends CompletionStage<T>> fn,
T initialValue,
Predicate<? super T> shouldContinue) |
Repeatedly applies an asynchronous function
fn to a value until shouldContinue
returns false , unconditionally applying fn to initialValue on the first
iteration. |
static <T> CompletionStage<T> |
asyncWhile(Predicate<? super T> shouldContinue,
Function<? super T,? extends CompletionStage<T>> fn,
T initialValue) |
Repeatedly applies an asynchronous function
fn to a value until shouldContinue
returns false . |
static CompletionStage<Void> |
asyncWhile(Supplier<? extends CompletionStage<Boolean>> fn) |
Repeatedly uses the function
fn to produce a CompletionStage of a boolean,
stopping when then boolean is false . |
public static <T> CompletionStage<T> asyncWhile(Predicate<? super T> shouldContinue, Function<? super T,? extends CompletionStage<T>> fn, T initialValue)
fn
to a value until shouldContinue
returns false
. The asynchronous equivalent of
T loop(Predicate shouldContinue, Function fn, T initialValue) {
T t = initialValue;
while (shouldContinue.test(t)) {
t = fn.apply(t);
}
return t;
}
Effectively produces fn(seed).thenCompose(fn).thenCompose(fn)... .thenCompose(fn)
until
an value fails the predicate. Note that predicate will be applied on seed (like a while loop,
the initial value is tested). For a do/while style see
asyncDoWhile(Function, Object, Predicate)
. If the predicate or fn throw an exception,
or the CompletionStage
returned by fn completes exceptionally, iteration will stop and
an exceptional stage will be returned.
T
- the type of elements produced by the loopshouldContinue
- a predicate which will be applied to every intermediate T value
(including the initialValue
) until it fails and looping stops.fn
- the function for the loop body which produces a new CompletionStage
based on
the result of the previous iteration.initialValue
- the value that will initially be passed to fn
, it will also be
initially tested by shouldContinue
CompletionStage
that completes with the first value t such that
shouldContinue.test(T) == false
, or with an exception if one was thrown.public static CompletionStage<Void> asyncWhile(Supplier<? extends CompletionStage<Boolean>> fn)
fn
to produce a CompletionStage
of a boolean,
stopping when then boolean is false
. The asynchronous equivalent of
while(fn.get());
. Generally, the function fn must perform some side effect for this method to
be useful. If the fn
throws or produces an exceptional CompletionStage
, an
exceptional stage will be returned.fn
- a Supplier
of a CompletionStage
that indicates whether iteration
should continueCompletionStage
that is complete when a stage produced by fn
has
returned false
, or with an exception if one was thrownpublic static <T> CompletionStage<T> asyncDoWhile(Function<? super T,? extends CompletionStage<T>> fn, T initialValue, Predicate<? super T> shouldContinue)
fn
to a value until shouldContinue
returns false
, unconditionally applying fn to initialValue
on the first
iteration. The asynchronous equivalent of
{
T t = initialValue;
do {
t = fn.apply(t);
} while(shouldContinue.test(t));
return t;
}
Effectively produces fn(seed).thenCompose(fn).thenCompose(fn)... .thenCompose(fn)
until
an value fails the predicate. Note that predicate will be applied on seed (like a while loop,
Effectively produces fn(seed).then(fn).flatMap(fn)... .flatMap(fn) until an value fails the
predicate. Note that predicate will not be applied to initialValue
, for a while
style loop see asyncWhile(Predicate, Function, Object)
. If the predicate or fn throw
an exception, or the CompletionStage
returned by fn completes exceptionally, iteration
will stop and an exceptional stage will be returned.
T
- the type of elements produced by the loopshouldContinue
- a predicate which will be applied to intermediate T value other than the
initialValue
the in until it fails and looping stops.fn
- the function for the loop body which produces a new CompletionStage
based on
the result of the previous iteration.initialValue
- the value that will initially be passed to fn
, it will not be
initially tested by shouldContinue
CompletionStage
that completes with the first value t such that
shouldContinue.test(T) == false
, or with an exception if one was thrown.Copyright © 2018. All rights reserved.