T
- the type of promised valuepublic interface PromiseOperations<T>
These methods are available on Promise
and SuccessPromise
, but are defined on this separate interface for clarity.
Modifier and Type | Method and Description |
---|---|
<O> Promise<O> |
blockingMap(Function<? super T,? extends O> transformer)
Like
map(Function) , but performs the transformation on a blocking thread. |
Promise<T> |
cache()
Caches the promised value (or error) and returns it to all subscribers.
|
Promise<T> |
defer(Action<? super Runnable> releaser)
Allows the execution of the promise to be deferred to a later time.
|
<O> Promise<O> |
flatMap(Function<? super T,? extends Promise<O>> transformer)
Transforms the promised value by applying the given function to it that returns a promise for the transformed value.
|
<O> Promise<O> |
map(Function<? super T,? extends O> transformer)
Transforms the promised value by applying the given function to it.
|
Promise<T> |
onNull(NoArgAction action)
A convenience shorthand for
routing null values. |
Promise<T> |
onYield(Runnable onYield)
Registers a listener that is invoked when
this promise is initiated. |
Promise<T> |
route(Predicate<? super T> predicate,
Action<? super T> action)
Allows the promised value to be handled specially if it meets the given predicate, instead of being handled by the promise subscriber.
|
Promise<T> |
throttled(Throttle throttle)
Throttles
this promise, using the given throttle . |
Promise<T> |
wiretap(Action<? super Result<T>> listener)
Registers a listener for the promise outcome.
|
<O> Promise<O> map(Function<? super T,? extends O> transformer)
import ratpack.test.exec.ExecHarness;
import ratpack.test.exec.ExecResult;
import static org.junit.Assert.assertEquals;
public class Example {
public static void main(String... args) throws Exception {
ExecResult<String> result = ExecHarness.yieldSingle(c ->
c.blocking(() -> "foo")
.map(String::toUpperCase)
.map(s -> s + "-BAR")
);
assertEquals("FOO-BAR", result.getValue());
}
}
O
- the type of the transformed objecttransformer
- the transformation to apply to the promised value<O> Promise<O> blockingMap(Function<? super T,? extends O> transformer)
map(Function)
, but performs the transformation on a blocking thread.
This is simply a more convenient form of using ExecControl.blocking(java.util.concurrent.Callable)
and flatMap(Function)
.
O
- the type of the transformed objecttransformer
- the transformation to apply to the promised value, on a blocking thread<O> Promise<O> flatMap(Function<? super T,? extends Promise<O>> transformer)
This is useful when the transformation involves an asynchronous operation.
import ratpack.test.exec.ExecHarness;
import ratpack.test.exec.ExecResult;
import static org.junit.Assert.assertEquals;
public class Example {
public static void main(String[] args) throws Exception {
ExecResult<String> result = ExecHarness.yieldSingle(c ->
c.blocking(() -> "foo")
.flatMap(s -> c.blocking(s::toUpperCase))
.map(s -> s + "-BAR")
);
assertEquals("FOO-BAR", result.getValue());
}
}
In the above example, flatMap()
is being used because the transformation requires a blocking operation (it doesn't really in this case, but that's what the example is showing).
In this case, it would be more convenient to use blockingMap(Function)
.
O
- the type of the transformed objecttransformer
- the transformation to apply to the promised valueblockingMap(Function)
Promise<T> route(Predicate<? super T> predicate, Action<? super T> action)
This is typically used for validating values, centrally.
import com.google.common.collect.Lists;
import ratpack.test.exec.ExecHarness;
import ratpack.test.exec.ExecResult;
import java.util.List;
import static org.junit.Assert.*;
public class Example {
public static ExecResult<Integer> yield(int i, List<Integer> collector) throws Exception {
return ExecHarness.yieldSingle(c ->
c.<Integer>promise(f -> f.success(i))
.route(v -> v > 5, collector::add)
);
}
public static void main(String... args) throws Exception {
List<Integer> routed = Lists.newLinkedList();
ExecResult<Integer> result1 = yield(1, routed);
assertEquals(new Integer(1), result1.getValue());
assertFalse(result1.isComplete()); // false because promise returned a value before the execution completed
assertTrue(routed.isEmpty());
ExecResult<Integer> result10 = yield(10, routed);
assertNull(result10.getValue());
assertTrue(result10.isComplete()); // true because the execution completed before the promised value was returned (i.e. it was routed)
assertTrue(routed.contains(10));
}
}
Be careful about using this where the eventual promise subscriber is unlikely to know that the promise will routed as it can be surprising when neither the promised value nor an error appears.
It can be useful at the handler layer to provide common validation.
import ratpack.exec.Promise;
import ratpack.handling.Context;
import ratpack.test.embed.EmbeddedApp;
import static org.junit.Assert.assertEquals;
public class Example {
public static Promise<Integer> getAge(Context ctx) {
return ctx
.blocking(() -> 10) // e.g. fetch value from DB
.route(
i -> i < 21,
i -> ctx.render(i + " is too young to be here!")
);
}
public static void main(String... args) throws Exception {
EmbeddedApp.fromHandler(ctx ->
getAge(ctx).then(age -> ctx.render("welcome!"))
).test(httpClient -> {
assertEquals("10 is too young to be here!", httpClient.getText());
});
}
}
If the routed-to action throws an exception, it will be forwarded down the promise chain.
predicate
- the condition under which the value should be routedaction
- the terminal action for the valuePromise<T> onNull(NoArgAction action)
routing
null
values.
If the promised value is null
, the given action will be called.
action
- the action to route to if the promised value is nullPromise<T> cache()
import ratpack.exec.Promise;
import ratpack.test.exec.ExecHarness;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertTrue;
public class Example {
public static void main(String... args) throws Exception {
ExecHarness.runSingle(c -> {
AtomicInteger counter = new AtomicInteger();
Promise<Integer> uncached = c.promise(f -> f.success(counter.getAndIncrement()));
uncached.then(i -> assertTrue(i == 0));
uncached.then(i -> assertTrue(i == 1));
uncached.then(i -> assertTrue(i == 2));
Promise<Integer> cached = uncached.cache();
cached.then(i -> assertTrue(i == 3));
cached.then(i -> assertTrue(i == 3));
uncached.then(i -> assertTrue(i == 4));
cached.then(i -> assertTrue(i == 3));
});
}
}
If the cached promise fails, the same exception will be returned every time.
import ratpack.exec.Promise;
import ratpack.test.exec.ExecHarness;
import static org.junit.Assert.assertTrue;
public class Example {
public static void main(String... args) throws Exception {
ExecHarness.runSingle(c -> {
Throwable error = new Exception("bang!");
Promise<Object> cached = c.promise(f -> f.error(error)).cache();
cached.onError(t -> assertTrue(t == error)).then(i -> assertTrue("not called", false));
cached.onError(t -> assertTrue(t == error)).then(i -> assertTrue("not called", false));
cached.onError(t -> assertTrue(t == error)).then(i -> assertTrue("not called", false));
});
}
}
Promise<T> defer(Action<? super Runnable> releaser)
When the returned promise is subscribed to, the given releaser
action will be invoked.
The execution of this
promise is deferred until the runnable given to the releaser
is run.
It is generally more convenient to use throttled(Throttle)
or onYield(Runnable)
than this operation.
releaser
- the action that will initiate the execution some time laterPromise<T> onYield(Runnable onYield)
this
promise is initiated.
import com.google.common.collect.Lists;
import ratpack.test.exec.ExecHarness;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertEquals;
public class Example {
public static void main(String... args) throws Exception {
List<String> events = Lists.newLinkedList();
ExecHarness.runSingle(c ->
c.<String>promise(f -> {
events.add("promise");
f.success("foo");
})
.onYield(() -> events.add("onYield"))
.then(v -> events.add("then"))
);
assertEquals(Arrays.asList("onYield", "promise", "then"), events);
}
}
onYield
- the action to take when the promise is initiatedthis
promisePromise<T> wiretap(Action<? super Result<T>> listener)
import com.google.common.collect.Lists;
import ratpack.test.exec.ExecHarness;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertEquals;
public class Example {
public static void main(String... args) throws Exception {
List<String> events = Lists.newLinkedList();
ExecHarness.runSingle(c ->
c.<String>promise(f -> {
events.add("promise");
f.success("foo");
})
.wiretap(r -> events.add("wiretap: " + r.getValue()))
.then(v -> events.add("then"))
);
assertEquals(Arrays.asList("promise", "wiretap: foo", "then"), events);
}
}
listener
- the result listenerthis
promisePromise<T> throttled(Throttle throttle)
this
promise, using the given throttle
.
Throttling can be used to limit concurrency. Typically to limit concurrent use of an external resource, such as a HTTP API.
Note that the Throttle
instance given defines the actual throttling semantics.
import ratpack.exec.Throttle;
import ratpack.test.exec.ExecHarness;
import ratpack.test.exec.ExecResult;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertTrue;
public class Example {
public static void main(String... args) throws Exception {
int numJobs = 1000;
int maxAtOnce = 10;
ExecResult<Integer> result = ExecHarness.yieldSingle(c -> {
AtomicInteger maxConcurrent = new AtomicInteger();
AtomicInteger active = new AtomicInteger();
AtomicInteger done = new AtomicInteger();
Throttle throttle = Throttle.ofSize(maxAtOnce);
// Launch numJobs forked executions, and return the maximum number that were executing at any given time
return c.promise(f -> {
for (int i = 0; i < numJobs; i++) {
c.exec().start(e2 ->
c
.<Integer>promise(f2 -> {
int activeNow = active.incrementAndGet();
int maxConcurrentVal = maxConcurrent.updateAndGet(m -> Math.max(m, activeNow));
active.decrementAndGet();
f2.success(maxConcurrentVal);
})
.throttled(throttle) // limit concurrency
.then(max -> {
if (done.incrementAndGet() == numJobs) {
f.success(max);
}
}));
}
});
});
assertTrue(result.getValue() <= maxAtOnce);
}
}
throttle
- the particular throttle to use to throttle the operation