/*
* Copyright 2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ratpack.exec;
import ratpack.api.NonBlocking;
import ratpack.exec.internal.CachingUpstream;
import ratpack.exec.internal.DefaultExecution;
import ratpack.exec.internal.DefaultOperation;
import ratpack.exec.internal.DefaultPromise;
import ratpack.exec.util.Promised;
import ratpack.func.*;
import ratpack.util.Exceptions;
import java.time.Duration;
import java.util.Objects;
import static ratpack.func.Action.ignoreArg;
/**
* A promise for a single value.
* <p>
* A promise is a representation of a value which will become available later.
* Methods such as {@link #map(Function)}, {@link #flatMap(Function)}, {@link #cache()} etc.) allow a pipeline of “operations” to be specified,
* that the value will travel through as it becomes available.
* Such operations are implemented via the {@link #transform(Function)} method.
* Each operation returns a new promise object, not the original promise object.
* <p>
* To create a promise, use the {@link Promise#async(Upstream)} method (or one of the variants such as {@link Promise#sync(Factory)}.
* To test code that uses promises, use the {@link ratpack.test.exec.ExecHarness}.
* <p>
* The promise is not “activated” until the {@link #then(Action)} method is called.
* This method terminates the pipeline, and receives the final value.
* <p>
* Promise objects are multi use.
* Every promise pipeline has a value producing function at its start.
* Activating a promise (i.e. calling {@link #then(Action)}) invokes the function.
* The {@link #cache()} operation can be used to change this behaviour.
*
* @param <T> the type of promised value
*/
@SuppressWarnings("JavadocReference")
public interface Promise<T> {
/**
* Creates a promise for value that will be produced asynchronously.
* <p>
* The {@link Upstream#connect(Downstream)} method of the given upstream will be invoked every time the value is requested.
* This method should propagate the value (or error) to the given downstream object when it is available.
*
* <pre class="java">{@code
* import ratpack.exec.Promise;
* import ratpack.test.exec.ExecHarness;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
* public static void main(String[] args) throws Exception {
* String value = ExecHarness.yieldSingle(e ->
* Promise.<String>async(down ->
* new Thread(() -> {
* down.success("foo");
* }).start()
* )
* ).getValueOrThrow();
*
* assertEquals(value, "foo");
* }
* }
* }</pre>
*
* @param upstream the producer of the value
* @param <T> the type of promised value
* @return a promise for the asynchronously created value
* @see Upstream
* @see #sync(Factory)
* @see #value(Object)
* @see #error(Throwable)
* @since 1.3
*/
static <T> Promise<T> async(Upstream<T> upstream) {
return new DefaultPromise<>(DefaultExecution.upstream(upstream));
}
/**
* Creates a promise for the value synchronously produced by the given factory.
* <p>
* The given factory will be invoked every time that the value is requested.
* If the factory throws an exception, the promise will convey that exception.
*
* <pre class="java">{@code
* import ratpack.exec.Promise;
* import ratpack.test.exec.ExecHarness;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
* public static void main(String[] args) throws Exception {
* String value = ExecHarness.yieldSingle(e ->
* Promise.sync(() -> "foo")
* ).getValueOrThrow();
*
* assertEquals(value, "foo");
* }
* }
* }</pre>
*
* <p>
* This method is often used to when a method needs to return a promise, but can produce its value synchronously.
*
* @param factory the producer of the value
* @param <T> the type of promised value
* @return a promise for the result of the factory
* @see #async(Upstream)
* @see #value(Object)
* @see #error(Throwable)
* @since 1.3
*/
static <T> Promise<T> sync(Factory<T> factory) {
return new DefaultPromise<>(down ->
DefaultExecution.require().delimit(down::error, continuation -> {
T t;
try {
t = factory.create();
} catch (Exception e) {
continuation.resume(() -> down.error(e));
return;
}
continuation.resume(() -> down.success(t));
})
);
}
/**
* Creates a promise for the promise produced by the given factory.
* <p>
* The given factory will be invoked every time that the value is requested.
* If the factory throws an exception, the promise will convey that exception.
* <p>
* This can be used to effectively prepend work to another promise.
*
* @param factory the producer of the promise to return
* @param <T> the type of promised value
* @return a promise for the result of the factory
* @since 1.5
*/
static <T> Promise<T> flatten(Factory<? extends Promise<T>> factory) {
return new DefaultPromise<>(down ->
DefaultExecution.require().delimit(down::error, continuation -> {
Promise<T> promise;
try {
promise = factory.create();
} catch (Throwable e) {
continuation.resume(() -> down.error(e));
return;
}
continuation.resume(() -> promise.connect(down));
}
)
);
}
/**
* Deprecated. Use {@link #flatten(Factory)}.
*
* @param factory deprecated.
* @param <T> the type of promised value
* @return deprecated.
* @deprecated since 1.5, replaced by {@link #flatten(Factory)}.
*/
@Deprecated
static <T> Promise<T> wrap(Factory<? extends Promise<T>> factory) {
try {
return factory.create();
} catch (Exception e) {
return Promise.error(e);
}
}
/**
* Creates a promise for the given item.
* <p>
* The given item will be used every time that the value is requested.
*
* <pre class="java">{@code
* import ratpack.exec.Promise;
* import ratpack.test.exec.ExecHarness;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
* public static void main(String[] args) throws Exception {
* String value = ExecHarness.yieldSingle(e ->
* Promise.value("foo")
* ).getValueOrThrow();
*
* assertEquals(value, "foo");
* }
* }
* }</pre>
*
* @param t the promised value
* @param <T> the type of promised value
* @return a promise for the given item
* @see #async(Upstream)
* @see #sync(Factory)
* @see #error(Throwable)
*/
static <T> Promise<T> value(T t) {
return new DefaultPromise<>(down -> DefaultExecution.require().delimit(down::error, continuation ->
continuation.resume(() -> down.success(t))
));
}
/**
* A promise for {@code null}.
*
* @param <T> the type of promised value
* @return a promise for {@code null}.
* @since 1.5
*/
@SuppressWarnings("unchecked")
static <T> Promise<T> ofNull() {
return (Promise<T>) DefaultPromise.NULL;
}
/**
* Creates a failed promise with the given error.
* <p>
* The given error will be used every time that the value is requested.
*
* <pre class="java">{@code
* import ratpack.exec.Promise;
* import ratpack.test.exec.ExecHarness;
*
* import static org.junit.Assert.assertSame;
*
* public class Example {
* public static void main(String[] args) throws Exception {
* Exception exception = new Exception();
* Throwable error = ExecHarness.yieldSingle(e ->
* Promise.error(exception)
* ).getThrowable();
*
* assertSame(exception, error);
* }
* }
* }</pre>
*
* @param t the error
* @param <T> the type of promised value
* @return a failed promise
* @see #async(Upstream)
* @see #sync(Factory)
* @see #value(Object)
*/
static <T> Promise<T> error(Throwable t) {
return new DefaultPromise<>(down -> DefaultExecution.require().delimit(down::error, continuation ->
continuation.resume(() -> down.error(t))
));
}
/**
* Deprecated.
*
* @param upstream the producer of the value
* @param <T> the type of promised value
* @return a promise for the asynchronously created value
* @deprecated replaced by {@link #async(Upstream)}
*/
@Deprecated
static <T> Promise<T> of(Upstream<T> upstream) {
return async(upstream);
}
/**
* Deprecated.
*
* @param factory the producer of the value
* @param <T> the type of promised value
* @return a promise for the result of the factory
* @deprecated replaced by {@link #sync(Factory)}}
*/
@Deprecated
static <T> Promise<T> ofLazy(Factory<T> factory) {
return sync(factory);
}
/**
* Specifies what should be done with the promised object when it becomes available.
* <p>
* <b>Important:</b> this method can only be used from a Ratpack managed compute thread.
* If it is called on a non Ratpack managed compute thread it will immediately throw an {@link ExecutionException}.
*
* @param then the receiver of the promised value
* @throws ExecutionException if not called on a Ratpack managed compute thread
*/
void then(Action<? super T> then);
/**
* A low level hook for consuming the promised value.
* <p>
* It is generally preferable to use {@link #then(Action)} over this method.
*
* @param downstream the downstream consumer
*/
void connect(Downstream<? super T> downstream);
/**
* Apply a custom transform to this promise.
* <p>
* This method is the basis for the standard operations of this interface, such as {@link #map(Function)}.
* The following is a non generic implementation of a map that converts the value to upper case.
* <pre class="java">{@code
* import ratpack.test.exec.ExecHarness;
* import ratpack.exec.ExecResult;
* import ratpack.exec.Promise;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
* public static void main(String... args) throws Exception {
* ExecResult<String> result = ExecHarness.yieldSingle(c ->
* Promise.value("foo")
* .transform(up -> down ->
* up.connect(down.<String>onSuccess(value -> {
* try {
* down.success(value.toUpperCase());
* } catch (Throwable e) {
* down.error(e);
* }
* }))
* )
* );
*
* assertEquals("FOO", result.getValue());
* }
* }
* }</pre>
* <p>
* The “upstreamTransformer” function takes an upstream data source, and returns another upstream that wraps it.
* It is typical for the returned upstream to invoke the {@link Upstream#connect(Downstream)} method of the given upstream during its connect method.
* <p>
* For more examples of transform implementations, please see the implementations of the methods of this interface.
*
* @param upstreamTransformer a function that returns a new upstream, typically wrapping the given upstream argument
* @param <O> the type of item emitted by the transformed upstream
* @return a new promise
*/
<O> Promise<O> transform(Function<? super Upstream<? extends T>, ? extends Upstream<O>> upstreamTransformer);
/**
* Specifies the action to take if the an error occurs trying to produce the promised value, that the given predicate applies to.
* <p>
* If the given action throws an exception, the original exception will be rethrown with the exception thrown
* by the action added to the suppressed exceptions list.
*
* @param predicate the predicate to test against the error
* @param errorHandler the action to take if an error occurs
* @return A promise for the successful result
* @since 1.1
*/
default Promise<T> onError(Predicate<? super Throwable> predicate, Action<? super Throwable> errorHandler) {
return transform(up -> down ->
up.connect(down.onError(throwable -> {
if (predicate.apply(throwable)) {
try {
errorHandler.execute(throwable);
} catch (Throwable e) {
if (e != throwable) {
e.addSuppressed(throwable);
}
down.error(e);
return;
}
down.complete();
} else {
down.error(throwable);
}
}))
);
}
/**
* Specifies the action to take if the an error of the given type occurs trying to produce the promised value.
*
* <pre class="java">{@code
* import ratpack.http.TypedData;
* import ratpack.test.embed.EmbeddedApp;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
* public static void main(String... args) throws Exception {
* EmbeddedApp.fromHandler(ctx ->
* ctx.getRequest().getBody()
* .map(TypedData::getText)
* .map(t -> {
* if (t.equals("1")) {
* throw new IllegalArgumentException("validation error!");
* } else {
* throw new RuntimeException("some other error!");
* }
* })
* .onError(IllegalArgumentException.class, e -> ctx.render("the value is invalid"))
* .onError(e -> ctx.render("unknown error: " + e.getMessage()))
* .then(t -> ctx.render("ok"))
* ).test(httpClient -> {
* assertEquals(httpClient.requestSpec(r -> r.getBody().text("0")).postText(), "unknown error: some other error!");
* assertEquals(httpClient.requestSpec(r -> r.getBody().text("1")).postText(), "the value is invalid");
* });
* }
* }
* }</pre>
* <p>
* If the given action throws an exception, the original exception will be rethrown with the exception thrown
* by the action added to the suppressed exceptions list.
*
* @param errorType the type of exception to handle with the given action
* @param errorHandler the action to take if an error occurs
* @param <E> the type of exception to handle with the given action
* @return A promise for the successful result
* @since 1.1
*/
default <E extends Throwable> Promise<T> onError(Class<E> errorType, Action<? super E> errorHandler) {
return onError(errorType::isInstance, t -> errorHandler.execute(errorType.cast(t)));
}
/**
* Specifies the action to take if the an error occurs trying to produce the promised value.
* <p>
* If the given action throws an exception, the original exception will be rethrown with the exception thrown
* by the action added to the suppressed exceptions list.
*
* @param errorHandler the action to take if an error occurs
* @return A promise for the successful result
*/
default Promise<T> onError(Action<? super Throwable> errorHandler) {
return onError(Predicate.TRUE, errorHandler);
}
/**
* Consume the promised value as a {@link Result}.
* <p>
* This method is an alternative to {@link #then(Action)} and {@link #onError(Action)}.
*
* @param resultHandler the consumer of the result
*/
default void result(Action<? super ExecResult<T>> resultHandler) {
connect(new Downstream<T>() {
@Override
public void success(T value) {
try {
resultHandler.execute(ExecResult.of(Result.success(value)));
} catch (Throwable e) {
DefaultPromise.throwError(e);
}
}
@Override
public void error(Throwable throwable) {
try {
resultHandler.execute(ExecResult.of(Result.<T>error(throwable)));
} catch (Throwable e) {
DefaultPromise.throwError(e);
}
}
@Override
public void complete() {
try {
resultHandler.execute(ExecResult.<T>complete());
} catch (Throwable e) {
DefaultPromise.throwError(e);
}
}
});
}
/**
* Transforms the promised value by applying the given function to it.
* <pre class="java">{@code
* import ratpack.test.exec.ExecHarness;
* import ratpack.exec.ExecResult;
* import ratpack.exec.Promise;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
* public static void main(String... args) throws Exception {
* ExecResult<String> result = ExecHarness.yieldSingle(c ->
* Promise.value("foo")
* .map(String::toUpperCase)
* .map(s -> s + "-BAR")
* );
*
* assertEquals("FOO-BAR", result.getValue());
* }
* }
* }</pre>
*
* @param transformer the transformation to apply to the promised value
* @param <O> the type of the transformed object
* @return a promise for the transformed value
*/
default <O> Promise<O> map(Function<? super T, ? extends O> transformer) {
return transform(up -> down -> up.connect(
down.<T>onSuccess(value -> {
try {
O apply = transformer.apply(value);
down.success(apply);
} catch (Throwable e) {
down.error(e);
}
})
)
);
}
/**
* Transforms the promised value by applying the given function to it, if it satisfies the predicate.
*
* <pre class="java">{@code
* import ratpack.test.exec.ExecHarness;
* import ratpack.exec.ExecResult;
* import ratpack.exec.Promise;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
* public static void main(String... args) throws Exception {
* ExecResult<String> result = ExecHarness.yieldSingle(c ->
* Promise.value("foo")
* .mapIf(s -> s.contains("f"), String::toUpperCase)
* .mapIf(s -> s.contains("f"), s -> s + "-BAR")
* );
*
* assertEquals("FOO", result.getValue());
* }
* }
* }</pre>
*
* @param predicate the condition to satisfy in order to be transformed
* @param transformer the transformation to apply to the promised value
* @return a promise
* @since 1.4
*/
default Promise<T> mapIf(Predicate<? super T> predicate, Function<? super T, ? extends T> transformer) {
return map(Function.when(predicate, transformer));
}
/**
* Transforms the promised value by applying one of the given functions to it, depending if it satisfies the predicate.
*
* <pre class="java">{@code
* import ratpack.test.exec.ExecHarness;
* import ratpack.exec.ExecResult;
* import ratpack.exec.Promise;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
* public static void main(String... args) throws Exception {
* ExecResult<String> result = ExecHarness.yieldSingle(c ->
* Promise.value("foo")
* .mapIf(s -> s.contains("f"), String::toUpperCase, s -> s)
* .mapIf(s -> s.contains("f"), s -> s, s -> s + "-BAR")
* );
*
* assertEquals("FOO-BAR", result.getValue());
* }
* }
* }</pre>
*
* @param predicate the condition to decide which transformation to apply
* @param onTrue the transformation to apply when the predicate is true
* @param onFalse the transformation to apply when the predicate is false
* @return a promise
* @since 1.5
*/
default Promise<T> mapIf(Predicate<? super T> predicate, Function<? super T, ? extends T> onTrue, Function<? super T, ? extends T> onFalse) {
return map(Function.when(predicate, onTrue, onFalse));
}
/**
* Like {@link #map(Function)}, but performs the transformation on a blocking thread.
* <p>
* This is simply a more convenient form of using {@link Blocking#get(Factory)} and {@link #flatMap(Function)}.
*
* @param transformer the transformation to apply to the promised value, on a blocking thread
* @param <O> the type of the transformed object
* @return a promise for the transformed value
*/
default <O> Promise<O> blockingMap(Function<? super T, ? extends O> transformer) {
return flatMap(t -> Blocking.get(() -> transformer.apply(t)));
}
/**
* Executes the given action with the promise value, on a blocking thread.
* <p>
* Similar to {@link #blockingMap(Function)}, but does not provide a new value.
* This can be used to do something with the value, without terminating the promise.
*
* @param action the action to to perform with the value, on a blocking thread
* @return a promise for the same value given to the action
*/
default Promise<T> blockingOp(Action<? super T> action) {
return flatMap(t -> Blocking.op(action.curry(t)).map(() -> t));
}
/**
* Deprecated.
* <p>
* Use {@link #replace(Promise)}.
*
* @param next the promise to replace {@code this} with
* @param <O> the type of value provided by the replacement promise
* @return a promise
* @deprecated replaced by {@link #replace(Promise)} as of 1.1.0
*/
@Deprecated
default <O> Promise<O> next(Promise<O> next) {
return flatMap(in -> next);
}
/**
* Executes the provided, potentially asynchronous, {@link Action} with the promised value as input.
* <p>
* This method can be used when needing to perform an action with the promised value, without substituting the promised value.
* That is, the exact same object provided to the given action will be propagated downstream.
* <p>
* The given action is executed within an {@link Operation}, allowing it to perform asynchronous work.
*
* <pre class="java">{@code
* import ratpack.test.exec.ExecHarness;
* import ratpack.exec.ExecResult;
* import ratpack.exec.Promise;
*
* import com.google.common.collect.Lists;
*
* import java.util.concurrent.TimeUnit;
* 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 ->
* Promise.value("foo")
* .next(v ->
* Promise.value(v) // may be async
* .map(String::toUpperCase)
* .then(events::add)
* )
* .then(events::add)
* );
* assertEquals(Arrays.asList("FOO", "foo"), events);
* }
* }
* }</pre>
*
* @param action the action to execute with the promised value
* @return a promise for the original value
* @see #nextOp(Function)
* @since 1.1
*/
default Promise<T> next(@NonBlocking Action<? super T> action) {
return nextOp(v ->
Operation.of(() ->
action.execute(v)
)
);
}
/**
* Executes the operation returned by the given function.
* <p>
* This method can be used when needing to perform an operation returned by another object, based on the promised value.
*
* <pre class="java">{@code
* import ratpack.test.exec.ExecHarness;
* import ratpack.exec.ExecResult;
* import ratpack.exec.Promise;
* import ratpack.exec.Operation;
*
* import com.google.common.collect.Lists;
*
* import java.util.concurrent.TimeUnit;
* import java.util.Arrays;
* import java.util.List;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
*
* public static class CaseService {
* public Operation toUpper(String value, List<String> values) {
* return Operation.of(() -> values.add(value.toUpperCase()));
* }
* }
*
* public static void main(String... args) throws Exception {
* CaseService service = new CaseService();
* List<String> events = Lists.newLinkedList();
*
* ExecHarness.runSingle(c ->
* Promise.value("foo")
* .nextOp(v -> service.toUpper(v, events))
* .then(events::add)
* );
*
* assertEquals(Arrays.asList("FOO", "foo"), events);
* }
* }
* }</pre>
*
* @param function a function that returns an operation that acts on the promised value
* @return a promise for the original value
* @see #next(Action)
* @since 1.1
*/
default Promise<T> nextOp(Function<? super T, ? extends Operation> function) {
return transform(up -> down -> up.connect(
down.<T>onSuccess(value ->
function.apply(value)
.onError(down::error)
.then(() ->
down.success(value)
)
)
)
);
}
/**
* Executes the operation returned by the given function, if it satisfies the predicate.
* <p>
* This method can be used when needing to perform an operation returned by another object, based on the promised value.
*
* <pre class="java">{@code
* import ratpack.test.exec.ExecHarness;
* import ratpack.exec.ExecResult;
* import ratpack.exec.Promise;
* import ratpack.exec.Operation;
*
* import com.google.common.collect.Lists;
*
* import java.util.concurrent.TimeUnit;
* import java.util.Arrays;
* import java.util.List;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
*
* public static class CaseService {
* public Operation toUpper(String value, List<String> values) {
* return Operation.of(() -> values.add(value.toUpperCase()));
* }
* }
*
* public static void main(String... args) throws Exception {
* CaseService service = new CaseService();
* List<String> events = Lists.newLinkedList();
*
* ExecHarness.runSingle(c ->
* Promise.value("foo")
* .nextOpIf(v -> v.startsWith("f"), v -> service.toUpper(v, events))
* .nextOpIf(v -> v.isEmpty(), v -> Operation.of(() -> events.add("empty")))
* .then(events::add)
* );
*
* assertEquals(Arrays.asList("FOO", "foo"), events);
* }
* }
* }</pre>
*
* @param predicate the condition to satisfy in order to execute the operation.
* @param function a function that returns an operation that acts on the promised value
* @return a promise for the original value
* @since 1.5
*/
default Promise<T> nextOpIf(Predicate<? super T> predicate, Function<? super T, ? extends Operation> function) {
return transform(up -> down -> up.connect(
down.<T>onSuccess(value -> {
if (predicate.apply(value)) {
function.apply(value)
.onError(down::error)
.then(() -> down.success(value));
} else {
down.success(value);
}
})
)
);
}
/**
* Replaces {@code this} promise with the provided promise for downstream subscribers.
* <p>
* This is simply a more convenient form of {@link #flatMap(Function)}, where the given promise is returned.
* This method can be used when a subsequent operation on a promise isn't dependent on the actual promised value.
* <p>
* If the upstream promise fails, its error will propagate downstream and the given promise will never be subscribed to.
*
* <pre class="java">{@code
* import ratpack.test.exec.ExecHarness;
* import ratpack.exec.ExecResult;
* import ratpack.exec.Promise;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
* private static String value;
*
* public static void main(String... args) throws Exception {
* ExecResult<String> result = ExecHarness.yieldSingle(c ->
* Promise.value("foo")
* .next(v -> value = v)
* .replace(Promise.value("bar"))
* );
*
* assertEquals("bar", result.getValue());
* assertEquals("foo", value);
* }
* }
* }</pre>
*
* @param next the promise to replace {@code this} with
* @param <O> the type of the value of the replacement promise
* @return a promise
* @since 1.1
*/
default <O> Promise<O> replace(Promise<O> next) {
return flatMap(in -> next);
}
/**
* Transforms the promised value to a {@link Pair}, with the value of the given promise as the {@code left}.
* <p>
* The existing promised value will become the {@code right}.
*
* @param left a promise for the left value of the result pair
* @param <O> the type of the left value
* @return a promise
*/
default <O> Promise<Pair<O, T>> left(Promise<O> left) {
return flatLeft(t -> left);
}
/**
* Transforms the promised value to a {@link Pair}, with the result of the given function as the {@code left}.
* <p>
* The function is called with the promised value.
* The existing promised value will become the {@code right}.
*
* @param leftFunction a function that produces the left value from the promised value
* @param <O> the type of the left value
* @return a promise
* @since 1.4
*/
default <O> Promise<Pair<O, T>> left(Function<? super T, ? extends O> leftFunction) {
return map(right -> Pair.of(
leftFunction.apply(right), right
));
}
/**
* Transforms the promised value to a {@link Pair}, with the value of the result of the given function as the {@code left}.
* <p>
* The function is called with the promised value.
* The existing promised value will become the {@code right}.
*
* @param leftFunction a function that produces a promise for the left value from the promised value
* @param <O> the type of the left value
* @return a promise
* @since 1.4
*/
default <O> Promise<Pair<O, T>> flatLeft(Function<? super T, ? extends Promise<O>> leftFunction) {
return flatMap(right ->
leftFunction.apply(right)
.map(left ->
Pair.of(left, right)
)
);
}
/**
* Transforms the promised value to a {@link Pair}, with the value of the given promise as the {@code right}.
* <p>
* The existing promised value will become the {@code left}.
*
* @param right a promise for the right value of the result pair
* @param <O> the type of the right value
* @return a promise
*/
default <O> Promise<Pair<T, O>> right(Promise<O> right) {
return flatRight(t -> right);
}
/**
* Transforms the promised value to a {@link Pair}, with the result of the given function as the {@code right}.
* <p>
* The function is called with the promised value.
* The existing promised value will become the {@code left}.
*
* @param rightFunction a function that produces the right value from the promised value
* @param <O> the type of the left value
* @return a promise
* @since 1.4
*/
default <O> Promise<Pair<T, O>> right(Function<? super T, ? extends O> rightFunction) {
return map(left -> Pair.of(
left, rightFunction.apply(left)
));
}
/**
* Transforms the promised value to a {@link Pair}, with the value of the result of the given function as the {@code right}.
* <p>
* The function is called with the promised value.
* The existing promised value will become the {@code left}.
*
* @param rightFunction a function that produces a promise for the right value from the promised value
* @param <O> the type of the left value
* @return a promise
* @since 1.4
*/
default <O> Promise<Pair<T, O>> flatRight(Function<? super T, ? extends Promise<O>> rightFunction) {
return flatMap(left ->
rightFunction.apply(left)
.map(right ->
Pair.of(left, right)
)
);
}
default Operation operation() {
return operation(Action.noop());
}
default Operation operation(Action<? super T> action) {
return new DefaultOperation(
map(t -> {
action.execute(t);
return null;
})
);
}
/**
* Transforms the promise failure (potentially into a value) by applying the given function to it.
* <p>
* If the function returns a value, the promise will now be considered successful.
* <pre class="java">{@code
* import ratpack.test.exec.ExecHarness;
* import ratpack.exec.ExecResult;
* import ratpack.exec.Promise;
* import static org.junit.Assert.assertEquals;
*
* public class Example {
* public static void main(String... args) throws Exception {
* ExecResult<String> result = ExecHarness.yieldSingle(c ->
* Promise.<String>error(new Exception("!"))
* .mapError(e -> "value")
* );
*
* assertEquals("value", result.getValue());
* }
* }
* }</pre>
* <p>
* If the function throws an exception, that exception will now represent the promise failure.
* <pre class="java">{@code
* import ratpack.test.exec.ExecHarness;
* import ratpack.exec.ExecResult;
* import ratpack.exec.Promise;
* import static org.junit.Assert.assertEquals;
*
* public class Example {
* public static void main(String... args) throws Exception {
* ExecResult<String> result = ExecHarness.yieldSingle(c ->
* Promise.<String>error(new Exception("!"))
* .mapError(e -> { throw new RuntimeException("mapped", e); })
* );
*
* assertEquals("mapped", result.getThrowable().getMessage());
* }
* }
* }</pre>
* <p>
* The function will not be called if the promise is successful.
*
* @param transformer the transformation to apply to the promise failure
* @return a promise
*/
default Promise<T> mapError(Function<? super Throwable, ? extends T> transformer) {
return transform(up -> down ->
up.connect(down.onError(throwable -> {
try {
T transformed = transformer.apply(throwable);
down.success(transformed);
} catch (Throwable t) {
down.error(t);
}
}))
);
}
/**
* Transforms a failure of the given type (potentially into a value) by applying the given function to it.
* <p>
* This method is similar to {@link #mapError(Function)}, except that it will only apply if the error is of the given type.
* If the error is not of the given type, it will not be transformed and will propagate as normal.
*
* @param function the transformation to apply to the promise failure
* @return a promise
* @since 1.3
*/
default <E extends Throwable> Promise<T> mapError(Class<E> type, Function<? super E, ? extends T> function) {
return transform(up -> down ->
up.connect(down.onError(throwable -> {
if (type.isInstance(throwable)) {
T transformed;
try {
transformed = function.apply(type.cast(throwable));
} catch (Throwable t) {
down.error(t);
return;
}
down.success(transformed);
} else {
down.error(throwable);
}
}))
);
}
/**
* Transforms a failure of the given type (potentially into a value) by applying the given function to it.
* <p>
* This method is similar to {@link #mapError(Function)}, except that it allows async transformation.
*
* @param function the transformation to apply to the promise failure
* @return a promise
* @since 1.3
*/
default Promise<T> flatMapError(Function<? super Throwable, ? extends Promise<T>> function) {
return transform(up -> down ->
up.connect(down.onError(throwable -> {
Promise<T> transformed;
try {
transformed = function.apply(throwable);
} catch (Throwable t) {
down.error(t);
return;
}
transformed.connect(down);
}))
);
}
/**
* Transforms a failure of the given type (potentially into a value) by applying the given function to it.
* <p>
* This method is similar to {@link #mapError(Class, Function)}, except that it allows async transformation.
*
* @param function the transformation to apply to the promise failure
* @return a promise
* @since 1.3
*/
default <E extends Throwable> Promise<T> flatMapError(Class<E> type, Function<? super E, ? extends Promise<T>> function) {
return transform(up -> down ->
up.connect(down.onError(throwable -> {
if (type.isInstance(throwable)) {
Promise<T> transformed;
try {
transformed = function.apply(type.cast(throwable));
} catch (Throwable t) {
down.error(t);
return;
}
transformed.connect(down);
} else {
down.error(throwable);
}
}))
);
}
/**
* Applies the custom operation function to this promise.
* <p>
* This method can be used to apply custom operations without breaking the “code flow”.
* It works particularly well with method references.
* <pre class="java">{@code
* import ratpack.exec.Promise;
* import ratpack.test.exec.ExecHarness;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
* public static void main(String... args) throws Exception {
* Integer value = ExecHarness.yieldSingle(e ->
* Promise.value(1)
* .apply(Example::dubble)
* .apply(Example::triple)
* ).getValue();
*
* assertEquals(Integer.valueOf(6), value);
* }
*
* public static Promise<Integer> dubble(Promise<Integer> input) {
* return input.map(i -> i * 2);
* }
*
* public static Promise<Integer> triple(Promise<Integer> input) {
* return input.map(i -> i * 3);
* }
* }
* }</pre>
* <p>
* If the apply function throws an exception, the returned promise will fail.
* <pre class="java">{@code
* import ratpack.exec.Promise;
* import ratpack.test.exec.ExecHarness;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
* public static void main(String... args) throws Exception {
* Throwable error = ExecHarness.yieldSingle(e ->
* Promise.value(1)
* .apply(Example::explode)
* ).getThrowable();
*
* assertEquals("bang!", error.getMessage());
* }
*
* public static Promise<Integer> explode(Promise<Integer> input) throws Exception {
* throw new Exception("bang!");
* }
* }
* }</pre>
* <p>
* If the promise having the operation applied to fails, the operation will not be applied.
* <pre class="java">{@code
* import ratpack.exec.Promise;
* import ratpack.test.exec.ExecHarness;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
* public static void main(String... args) throws Exception {
* Throwable error = ExecHarness.yieldSingle(e ->
* Promise.<Integer>error(new Exception("bang!"))
* .apply(Example::dubble)
* ).getThrowable();
*
* assertEquals("bang!", error.getMessage());
* }
*
* public static Promise<Integer> dubble(Promise<Integer> input) {
* return input.map(i -> i * 2);
* }
* }
* }</pre>
*
* @param <O> the type of promised object after the operation
* @param function the operation implementation
* @return the transformed promise
*/
default <O> Promise<O> apply(Function<? super Promise<T>, ? extends Promise<O>> function) {
try {
return function.apply(this);
} catch (Throwable e) {
return Promise.error(e);
}
}
/**
* Applies the given function to {@code this} and returns the result.
* <p>
* This method can be useful when needing to convert a promise to another type as it facilitates doing so without breaking the “code flow”.
* For example, this can be used when integrating with RxJava.
* <pre class="java">{@code
* import ratpack.rx.RxRatpack;
* import ratpack.exec.Promise;
* import ratpack.test.exec.ExecHarness;
*
* import java.util.Arrays;
* import java.util.LinkedList;
* import java.util.List;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
* private static final List<String> LOG = new LinkedList<>();
*
* public static void main(String... args) throws Exception {
* ExecHarness.runSingle(e ->
* Promise.value("foo")
* .to(RxRatpack::observe)
* .doOnNext(i -> LOG.add("doOnNext"))
* .subscribe(LOG::add)
* );
*
* assertEquals(Arrays.asList("doOnNext", "foo"), LOG);
* }
* }
* }</pre>
* <p>
* The given function is executed immediately.
* <p>
* This method should only be used when converting a promise to another type.
* See {@link #apply(Function)} for applying custom promise operators.
*
* @param function the promise conversion function
* @param <O> the type the promise will be converted to
* @return the output of the given function
* @throws Exception any thrown by the given function
*/
default <O> O to(Function<? super Promise<T>, ? extends O> function) throws Exception {
return function.apply(this);
}
/**
* Transforms the promised value by applying the given function to it that returns a promise for the transformed value.
* <p>
* This is useful when the transformation involves an asynchronous operation.
* <pre class="java">{@code
* import ratpack.test.exec.ExecHarness;
* import ratpack.exec.ExecResult;
* import ratpack.exec.Promise;
* import ratpack.exec.Blocking;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
* public static void main(String[] args) throws Exception {
* ExecResult<String> result = ExecHarness.yieldSingle(c ->
* Promise.value("foo")
* .flatMap(s -> Blocking.get(s::toUpperCase))
* .map(s -> s + "-BAR")
* );
*
* assertEquals("FOO-BAR", result.getValue());
* }
* }
* }</pre>
* <p>
*
* @param transformer the transformation to apply to the promised value
* @param <O> the type of the transformed object
* @return a promise for the transformed value
*/
default <O> Promise<O> flatMap(Function<? super T, ? extends Promise<O>> transformer) {
return transform(up -> down ->
up.connect(down.<T>onSuccess(value -> {
try {
transformer.apply(value).onError(down::error).then(down::success);
} catch (Throwable e) {
down.error(e);
}
}))
);
}
/**
* Transforms the promised value by applying the given function to it that returns a promise for the transformed value, if it satisfies the predicate.
*
* <pre class="java">{@code
* import ratpack.test.exec.ExecHarness;
* import ratpack.exec.ExecResult;
* import ratpack.exec.Promise;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
* public static void main(String... args) throws Exception {
* ExecResult<String> result = ExecHarness.yieldSingle(c ->
* Promise.value("foo")
* .flatMapIf(s -> s.contains("f"), s -> Promise.value(s.toUpperCase()))
* .flatMapIf(s -> s.contains("f"), s -> Promise.value(s + "-BAR"))
* );
*
* assertEquals("FOO", result.getValue());
* }
* }
* }</pre>
*
* @param predicate the condition to satisfy in order to be transformed
* @param transformer the transformation to apply to the promised value
* @return a promise
* @since 1.4
*/
default Promise<T> flatMapIf(Predicate<? super T> predicate, Function<? super T, ? extends Promise<T>> transformer) {
return flatMapIf(predicate, transformer, Promise::value);
}
/**
* Transforms the promised value by applying one of the given functions to it that returns a promise for the transformed value, depending if it satisfies the predicate.
*
* <pre class="java">{@code
* import ratpack.test.exec.ExecHarness;
* import ratpack.exec.ExecResult;
* import ratpack.exec.Promise;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
* public static void main(String... args) throws Exception {
* ExecResult<String> result = ExecHarness.yieldSingle(c ->
* Promise.value("foo")
* .flatMapIf(s -> s.contains("f"), s -> Promise.value(s.toUpperCase()), s -> Promise.value(s))
* .flatMapIf(s -> s.contains("f"), s -> Promise.value(s), s -> Promise.value(s + "-BAR"))
* );
*
* assertEquals("FOO-BAR", result.getValue());
* }
* }
* }</pre>
*
* @param predicate the condition to decide which transformation to apply
* @param onTrue the transformation to apply to the promised value when the predicate is true
* @param onFalse the transformation to apply to the promised value when the predicate is false
* @return a promise
* @since 1.5
*/
default Promise<T> flatMapIf(Predicate<? super T> predicate, Function<? super T, ? extends Promise<T>> onTrue, Function<? super T, ? extends Promise<T>> onFalse) {
return flatMap(Function.when(predicate, onTrue, onFalse));
}
/**
* Allows the promised value to be handled specially if it meets the given predicate, instead of being handled by the promise subscriber.
* <p>
* This is typically used for validating values, centrally.
* <pre class="java">{@code
* import com.google.common.collect.Lists;
* import ratpack.test.exec.ExecHarness;
* import ratpack.exec.ExecResult;
* import ratpack.exec.Promise;
*
* 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 ->
* Promise.value(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));
* }
* }
* }</pre>
* <p>
* 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.
* <p>
* It can be useful at the handler layer to provide common validation.
* <pre class="java">{@code
* 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 Promise.value(10)
* .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());
* });
* }
* }
* }</pre>
* <p>
* If the routed-to action throws an exception, it will be forwarded down the promise chain.
*
* @param predicate the condition under which the value should be routed
* @param action the terminal action for the value
* @return a routed promise
*/
default Promise<T> route(Predicate<? super T> predicate, Action<? super T> action) {
return transform(up -> down ->
up.connect(down.<T>onSuccess(value -> {
boolean apply;
try {
apply = predicate.apply(value);
} catch (Throwable e) {
down.error(e);
return;
}
if (apply) {
try {
action.execute(value);
down.complete();
} catch (Throwable e) {
down.error(e);
}
} else {
down.success(value);
}
}))
);
}
/**
* A convenience shorthand for {@link #route(Predicate, Action) routing} {@code null} values.
* <p>
* If the promised value is {@code null}, the given action will be called.
*
* @param action the action to route to if the promised value is null
* @return a routed promise
*/
default Promise<T> onNull(Block action) {
return route(Objects::isNull, ignoreArg(action));
}
/**
* Caches the promised value (or error) and returns it to all subscribers.
* <p>
* This method is equivalent to using {@link #cacheResultIf(Predicate)} with a predicate that always returns {@code true}.
*
* <pre class="java">{@code
* import ratpack.exec.Promise;
* import ratpack.test.exec.ExecHarness;
*
* import java.util.concurrent.atomic.AtomicLong;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
* public static void main(String... args) throws Exception {
* ExecHarness.runSingle(c -> {
* AtomicLong counter = new AtomicLong();
* Promise<Long> uncached = Promise.async(f -> f.success(counter.getAndIncrement()));
*
* uncached.then(i -> assertEquals(0l, i.longValue()));
* uncached.then(i -> assertEquals(1l, i.longValue()));
* uncached.then(i -> assertEquals(2l, i.longValue()));
*
* Promise<Long> cached = uncached.cache();
*
* cached.then(i -> assertEquals(3l, i.longValue()));
* cached.then(i -> assertEquals(3l, i.longValue()));
*
* uncached.then(i -> assertEquals(4l, i.longValue()));
* cached.then(i -> assertEquals(3l, i.longValue()));
* });
* }
* }
* }</pre>
*
* <p>
* If the cached promise fails, the same exception will be returned every time.
*
* <pre class="java">{@code
* 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 = Promise.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));
* });
* }
* }
* }</pre>
*
* @return a caching promise
* @see #cacheIf(Predicate)
* @see #cacheResultIf(Predicate)
* @see #cacheResultFor(Function)
*/
default Promise<T> cache() {
return cacheResultIf(Predicate.TRUE);
}
/**
* Caches the promise value and provides it to all future subscribers, if it satisfies the predicate.
* <p>
* This method is equivalent to using {@link #cacheResultIf(Predicate)} with a predicate that requires
* a successful result and for the value to satisfy the predicate given to this method.
* <p>
* Non success results will not be cached.
*
* @param shouldCache the test for whether a successful result is cacheable
* @return a caching promise
* @see #cacheResultIf(Predicate)
* @see #cacheResultFor(Function)
* @since 1.4
*/
default Promise<T> cacheIf(Predicate<? super T> shouldCache) {
return cacheResultIf(r -> r.isSuccess() && shouldCache.apply(r.getValue()));
}
/**
* Caches the promise result eternally and provide it to all future subscribers, if it satisfies the predicate.
*
* <pre class="java">{@code
* import ratpack.exec.ExecResult;
* import ratpack.exec.Promise;
* import ratpack.test.exec.ExecHarness;
*
* import java.util.ArrayList;
* import java.util.List;
* import java.util.concurrent.atomic.AtomicInteger;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
*
* public static void main(String... args) throws Exception {
* List<ExecResult<Integer>> results = new ArrayList<>();
* AtomicInteger counter = new AtomicInteger();
* Promise<Integer> promise = Promise.sync(() -> {
* int i = counter.getAndIncrement();
* if (i < 2) {
* return i;
* } else if (i == 2) {
* throw new Exception(Integer.toString(i));
* } else if (i == 3) {
* throw new RuntimeException(Integer.toString(i));
* } else {
* throw new IllegalStateException(Integer.toString(i));
* }
* });
*
* Promise<Integer> cachedPromise = promise.cacheResultIf(r ->
* (r.isError() && r.getThrowable().getClass() == RuntimeException.class)
* || (r.isSuccess() && r.getValue() > 10)
* );
*
* ExecHarness.runSingle(e -> {
* for (int i = 0; i < 6; i++) {
* cachedPromise.result(results::add);
* }
* });
*
* assertEquals(results.get(0).getValueOrThrow(), Integer.valueOf(0));
* assertEquals(results.get(1).getValueOrThrow(), Integer.valueOf(1));
* assertEquals(results.get(2).getThrowable().getClass(), Exception.class);
* assertEquals(results.get(3).getThrowable().getClass(), RuntimeException.class);
*
* // value is now cached
* assertEquals(results.get(4).getThrowable().getClass(), RuntimeException.class);
* assertEquals(results.get(5).getThrowable().getClass(), RuntimeException.class);
* }
* }
* }</pre>
*
* <p>
* Note, the cached value never expires.
* If you wish to cache for a certain amount of time, use {@link #cacheResultFor(Function)}.
*
* @param shouldCache the test for whether a result is cacheable
* @return a caching promise
* @see #cache()
* @see #cacheIf(Predicate)
* @see #cacheResultFor(Function)
* @since 1.4
*/
default Promise<T> cacheResultIf(Predicate<? super ExecResult<T>> shouldCache) {
return transform(up ->
new CachingUpstream<>(up, shouldCache.function(Duration.ofSeconds(-1), Duration.ZERO))
);
}
/**
* Caches the promise result for a calculated amount of time.
* <p>
* A cached promise is fully threadsafe and and can be subscribed to concurrently.
* While there is no valid cached value, yielding the upstream value is serialised.
* That is, one value is requested at a time regardless of concurrent subscription.
* <p>
* As the result is received, it is given to the {@code ttlFunc} which determines how long to cache it for.
* A {@link Duration#ZERO} duration indicates that the value should not be cached.
* Any {@link Duration#isNegative()} duration indicates that the value should be cached eternally.
* Any other duration indicates how long to cache the result for.
* <p>
* If the promise is subscribed to again after the cached value has expired, the process repeats.
* <p>
* As such promises tend to be held and reused, it is sometimes necessary to consider garbage collection implications.
* A caching promise (like all multi-use promises) must retain all of its upstream functions/objects.
* Care should be taken to ensure that this does not cause long lived references to objects that should be collected.
* <p>
* It is common to use cached promises in conjunction with a cache implementation such as Google Guava or Caffeine.
*
* <pre class="java">{@code
* import ratpack.exec.ExecResult;
* import ratpack.exec.Promise;
* import ratpack.test.exec.ExecHarness;
*
* import java.util.ArrayList;
* import java.util.List;
* import java.time.Duration;
* import java.util.concurrent.atomic.AtomicInteger;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
*
* public static void main(String... args) throws Exception {
* List<Integer> results = new ArrayList<>();
* AtomicInteger counter = new AtomicInteger();
* Promise<Integer> promise = Promise.sync(counter::getAndIncrement)
* .cacheResultFor(
* i -> i.isSuccess() && i.getValue() > 1
* ? Duration.ofSeconds(1)
* : Duration.ZERO
* );
*
* for (int i = 0; i < 4; ++i) {
* ExecHarness.runSingle(e -> promise.then(results::add));
* }
*
* // let the cache entry expire
* Thread.sleep(1500);
*
* for (int i = 0; i < 2; ++i) {
* ExecHarness.runSingle(e -> promise.then(results::add));
* }
*
* assertEquals(results.get(0), Integer.valueOf(0));
* assertEquals(results.get(1), Integer.valueOf(1));
* assertEquals(results.get(2), Integer.valueOf(2));
* assertEquals(results.get(3), Integer.valueOf(2));
*
* // cache entry has expired
*
* assertEquals(results.get(4), Integer.valueOf(3));
* assertEquals(results.get(5), Integer.valueOf(3));
* }
* }
* }</pre>
*
* @param cacheFor a function that determines how long to cache the given result for
* @return a caching promise
* @since 1.5
* @see #cache()
* @see #cacheIf(Predicate)
* @see #cacheResultIf(Predicate)
*/
default Promise<T> cacheResultFor(Function<? super ExecResult<T>, Duration> cacheFor) {
return transform(up -> new CachingUpstream<>(up, cacheFor));
}
/**
* Defers the subscription of {@code this} promise until later.
* <p>
* When the returned promise is subscribed to, the given {@code releaser} action will be invoked.
* The execution of {@code this} promise is deferred until the runnable given to the {@code releaser} is run.
* <p>
* It is important to note that this defers the <i>subscription</i> of the promise, not the delivery of the value.
* <p>
* It is generally more convenient to use {@link #throttled(Throttle)} or {@link #onYield(Runnable)} than this operation.
*
* @param releaser the action that will initiate the execution some time later
* @return a deferred promise
*/
default Promise<T> defer(Action<? super Runnable> releaser) {
return transform(up -> down ->
Promise.async(innerDown ->
releaser.execute((Runnable) () -> innerDown.success(true))
).then(v ->
up.connect(down)
)
);
}
/**
* Defers the subscription of {@code this} promise for the given duration.
* <p>
* This operation is roughly the promise based analog of {@link Execution#sleep(Duration, Block)}.
* <p>
* The given duration must be non-negative.
*
* @param duration the amount of time to defer for
* @return a deferred promise
* @see #defer(Action)
* @since 1.5
*/
default Promise<T> defer(Duration duration) {
return defer(r -> Execution.sleep(duration, r::run));
}
/**
* Registers a listener that is invoked when {@code this} promise is initiated.
* <pre class="java">{@code
* import com.google.common.collect.Lists;
* import ratpack.test.exec.ExecHarness;
* import ratpack.exec.Promise;
*
* 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 ->
* Promise.<String>sync(() -> {
* events.add("promise");
* return "foo";
* })
* .onYield(() -> events.add("onYield"))
* .then(v -> events.add("then"))
* );
* assertEquals(Arrays.asList("onYield", "promise", "then"), events);
* }
* }
* }</pre>
*
* @param onYield the action to take when the promise is initiated
* @return effectively, {@code this} promise
*/
default Promise<T> onYield(Runnable onYield) {
return transform(up -> down -> {
try {
onYield.run();
} catch (Throwable e) {
down.error(e);
return;
}
up.connect(down);
});
}
/**
* Registers a listener for the promise outcome.
* <pre class="java">{@code
* import com.google.common.collect.Lists;
* import ratpack.test.exec.ExecHarness;
* import ratpack.exec.Promise;
*
* 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 ->
* Promise.<String>sync(() -> {
* events.add("promise");
* return "foo";
* })
* .wiretap(r -> events.add("wiretap: " + r.getValue()))
* .then(v -> events.add("then"))
* );
*
* assertEquals(Arrays.asList("promise", "wiretap: foo", "then"), events);
* }
* }
* }</pre>
*
* @param listener the result listener
* @return effectively, {@code this} promise
*/
default Promise<T> wiretap(Action<? super Result<T>> listener) {
return transform(up -> down ->
up.connect(new Downstream<T>() {
@Override
public void success(T value) {
try {
listener.execute(Result.success(value));
} catch (Exception e) {
down.error(e);
return;
}
down.success(value);
}
@Override
public void error(Throwable throwable) {
try {
listener.execute(Result.<T>error(throwable));
} catch (Exception e) {
throwable.addSuppressed(e);
}
down.error(throwable);
}
@Override
public void complete() {
down.complete();
}
})
);
}
/**
* Throttles {@code this} promise, using the given {@link Throttle throttle}.
* <p>
* Throttling can be used to limit concurrency.
* Typically to limit concurrent use of an external resource, such as a HTTP API.
* <p>
* Note that the {@link Throttle} instance given defines the actual throttling semantics.
* <pre class="java">{@code
* import ratpack.exec.Throttle;
* import ratpack.exec.Promise;
* import ratpack.exec.Execution;
* import ratpack.test.exec.ExecHarness;
* import ratpack.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(exec -> {
* 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 Promise.async(downstream -> {
* for (int i = 0; i < numJobs; i++) {
* Execution.fork().start(forkedExec ->
* Promise.sync(() -> {
* int activeNow = active.incrementAndGet();
* int maxConcurrentVal = maxConcurrent.updateAndGet(m -> Math.max(m, activeNow));
* active.decrementAndGet();
* return maxConcurrentVal;
* })
* .throttled(throttle) // limit concurrency
* .then(max -> {
* if (done.incrementAndGet() == numJobs) {
* downstream.success(max);
* }
* })
* );
* }
* });
* });
*
* assertTrue(result.getValue() <= maxAtOnce);
* }
* }
* }</pre>
*
* @param throttle the particular throttle to use to throttle the operation
* @return the throttled promise
*/
default Promise<T> throttled(Throttle throttle) {
return throttle.throttle(this);
}
/**
* Closes the given closeable when the value or error propagates to this point.
* <p>
* This can be used to simulate a try/finally synchronous construct.
* It is typically used to close some resource after an asynchronous operation.
*
* <pre class="java">{@code
* import org.junit.Assert;
* import ratpack.exec.Promise;
* import ratpack.test.exec.ExecHarness;
*
* public class Example {
* static class MyResource implements AutoCloseable {
* final boolean inError;
* boolean closed;
*
* public MyResource(boolean inError) {
* this.inError = inError;
* }
*
* {@literal @}Override
* public void close() {
* closed = true;
* }
* }
*
* static Promise<String> resourceUsingMethod(MyResource resource) {
* return Promise.sync(() -> {
* if (resource.inError) {
* throw new Exception("error!");
* } else {
* return "ok!";
* }
* });
* }
*
* public static void main(String[] args) throws Exception {
* ExecHarness.runSingle(e -> {
* MyResource myResource = new MyResource(false);
* resourceUsingMethod(myResource)
* .close(myResource)
* .then(value -> Assert.assertTrue(myResource.closed));
* });
*
* ExecHarness.runSingle(e -> {
* MyResource myResource = new MyResource(true);
* resourceUsingMethod(myResource)
* .close(myResource)
* .onError(error -> Assert.assertTrue(myResource.closed))
* .then(value -> {
* throw new UnsupportedOperationException("should not reach here!");
* });
* });
*
* }
* }
* }</pre>
* <p>
* The general pattern is to open the resource, and then pass it to some method/closure that works with it and returns a promise.
* This method is then called on the returned promise to cleanup the resource.
*
* @param closeable the closeable to close
* @see #close(Operation)
* @return a promise
* @since 1.3
*/
default Promise<T> close(AutoCloseable closeable) {
return transform(up -> down ->
up.connect(new Downstream<T>() {
@Override
public void success(T value) {
try {
closeable.close();
} catch (Exception e) {
down.error(e);
return;
}
down.success(value);
}
@Override
public void error(Throwable throwable) {
try {
closeable.close();
} catch (Exception e) {
throwable.addSuppressed(e);
}
down.error(throwable);
}
@Override
public void complete() {
try {
closeable.close();
} catch (Exception e) {
down.error(e);
return;
}
down.complete();
}
})
);
}
/**
* Like {@link #close(AutoCloseable)}, but allows async close operations.
*
* @param closer the close operation.
* @return a promise
* @since 1.5
*/
default Promise<T> close(Operation closer) {
return transform(up -> down ->
up.connect(new Downstream<T>() {
@Override
public void success(T value) {
closer.promise().connect(new Downstream<Void>() {
@Override
public void success(Void v) {
down.success(value);
}
@Override
public void error(Throwable throwable) {
down.error(throwable);
}
@Override
public void complete() {
down.success(value);
}
});
}
@Override
public void error(Throwable throwable) {
closer.promise().connect(new Downstream<Void>() {
@Override
public void success(Void v) {
down.error(throwable);
}
@Override
public void error(Throwable innerThrowable) {
innerThrowable.addSuppressed(throwable);
down.error(innerThrowable);
}
@Override
public void complete() {
down.error(throwable);
}
});
}
@Override
public void complete() {
closer.promise().connect(new Downstream<Void>() {
@Override
public void success(Void v) {
down.complete();
}
@Override
public void error(Throwable innerThrowable) {
down.error(innerThrowable);
}
@Override
public void complete() {
down.complete();
}
});
}
})
);
}
/**
* Emits the time taken from when the promise is subscribed to to when the result is available.
* <p>
* The given {@code action} is called regardless of whether the promise is successful or not.
* <p>
* If the promise fails and this method throws an exception, the original exception will propagate with the thrown exception suppressed.
* If the promise succeeds and this method throws an exception, the thrown exception will propagate.
*
* @param action a callback for the time
* @since 1.3
* @see #timeResult(BiAction)
* @return effectively {@code this}
*/
default Promise<T> time(Action<? super Duration> action) {
return timeResult((r, d) -> action.execute(d));
}
/**
* Emits the time taken from when the promise is subscribed to to when the result is available.
* <p>
* The given {@code action} is called regardless of whether the promise is successful or not.
* <p>
* If the promise fails and this method throws an exception, the original exception will propagate with the thrown exception suppressed.
* If the promise succeeds and this method throws an exception, the thrown exception will propagate.
*
* @param action a callback for the time
* @since 1.5
* @see #time(Action)
* @return effectively {@code this}
*/
default Promise<T> timeResult(BiAction<? super ExecResult<T>, ? super Duration> action) {
return around(System::nanoTime, (start, result) -> {
// protect against clock skew causing negative durations
Duration duration = Duration.ofNanos(Math.max(0, System.nanoTime() - start));
action.execute(result, duration);
return result;
});
}
default Promise<Pair<ExecResult<T>, Duration>> timeResult() {
return around(System::nanoTime, (start, result) -> {
// protect against clock skew causing negative durations
Duration duration = Duration.ofNanos(Math.max(0, System.nanoTime() - start));
return ExecResult.of(Result.success(Pair.of(result, duration)));
});
}
/**
* Facilitates capturing a value before the the promise is subscribed and using it to later augment the result.
* <p>
* The {@code before} factory is invoked as the promise is subscribed.
* As the promise result becomes available, it and the result are given to the {@code after} function.
* The return value of the {@code after} function forms the basis of the promise returned from this method.
*
* @param before the before value supplier
* @param after the after function
* @param <B> the before value type
* @param <A> the after value type
* @return a promise
* @since 1.5
*/
default <B, A> Promise<A> around(Factory<? extends B> before, BiFunction<? super B, ? super ExecResult<T>, ? extends ExecResult<A>> after) {
return transform(up -> down -> {
B start;
try {
start = before.create();
} catch (Throwable e) {
down.error(e);
return;
}
up.connect(new Downstream<T>() {
private void onResult(ExecResult<T> originalResult) {
ExecResult<A> newResult;
try {
newResult = after.apply(start, originalResult);
} catch (Throwable t) {
if (originalResult.isError() && originalResult.getThrowable() != t) {
t.addSuppressed(originalResult.getThrowable());
}
down.error(t);
return;
}
down.accept(newResult);
}
@Override
public void success(T value) {
onResult(ExecResult.of(Result.success(value)));
}
@Override
public void error(Throwable throwable) {
onResult(ExecResult.of(Result.error(throwable)));
}
@Override
public void complete() {
onResult(ExecResult.complete());
}
});
});
}
/**
* Forks a new execution and subscribes to this promise, returning a promise for its value.
* <p>
* The new execution is created and started immediately by this method, effectively subscribing to the promise immediately.
* The returned promise provides the value when the execution completes.
* <p>
* This method can be used for simple of processing.
* It is often combined with the {@link #left(Promise)} or {@link #right(Promise)}.
*
* <pre class="java">{@code
* import ratpack.exec.Blocking;
* import ratpack.exec.Promise;
* import ratpack.func.Pair;
* import ratpack.test.exec.ExecHarness;
*
* import java.util.concurrent.CyclicBarrier;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
*
* public static void main(String... args) throws Exception {
* CyclicBarrier barrier = new CyclicBarrier(2);
*
* Pair<Integer, String> result = ExecHarness.yieldSingle(r -> {
* Promise<Integer> p1 = Blocking.get(() -> {
* barrier.await();
* return 1;
* });
* Promise<String> p2 = Blocking.get(() -> {
* barrier.await();
* return "2";
* });
*
* return p1.right(p2.fork());
* }).getValueOrThrow();
*
* assertEquals(result, Pair.of(1, "2"));
* }
*
* }
* }</pre>
* <p>
* <b>Warning:</b> be mindful of error handling for forked promises.
* If the forked promise is never subscribed to, its failure may go unnoticed.
* In scenarios, where it cannot be guaranteed that the forked promise will be subscribed to
* or that subscribers would satisfactorily deal with error conditions,
* consider <i>listening</i> for errors by using {@link #wiretap(Action)} before {@link #fork()}
* and logging the error or similar.
*
* @param execSpec configuration for the forked execution
* @return a promise
* @throws Exception any thrown by {@code execSpec}
* @since 1.4
*/
default Promise<T> fork(Action<? super ExecSpec> execSpec) throws Exception {
Promised<T> promised = new Promised<>();
execSpec.with(Execution.fork()).start(e -> connect(promised));
return promised.promise();
}
/**
* Forks a new execution and subscribes to this promise, returning a promise for its value.
* <p>
* This method delegates to {@link #fork(Action)} with {@link Action#noop()}.
*
* @return a promise
* @since 1.4
* @see #fork(Action)
*/
default Promise<T> fork() {
return Exceptions.uncheck(() -> fork(Action.noop()));
}
/**
* Causes {@code this} yielding the promised value to be retried on error, after a fixed delay.
* <p>
* The given function is invoked for each failure,
* with the sequence number of the failure as the first argument and the failure exception as the second.
* This may be used to log or collect exceptions.
* If all errors are to be ignored, use {@link BiAction#noop()}.
* <p>
* Any exception thrown by the function – possibly the exception it receives as an argument – will
* be propagated to the subscriber, yielding a failure.
* This can be used to selectively retry on certain failures, but immediately fail on others.
* <p>
* If the promise fails {@code maxAttempts} times,
* the given function will not be invoked and the most and the most recent exception will propagate.
* <p>
* To retry immediately, pass a zero duration.
* Use {@link #retry(int, BiFunction)} if desiring a dynamic (e.g. increasing) delay between attempts.
*
* <pre class="java">{@code
* import ratpack.exec.ExecResult;
* import ratpack.exec.Promise;
* import ratpack.test.exec.ExecHarness;
*
* import java.time.Duration;
* import java.util.Arrays;
* import java.util.LinkedList;
* import java.util.List;
* import java.util.concurrent.atomic.AtomicInteger;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
* private static final List<String> LOG = new LinkedList<>();
*
* public static void main(String... args) throws Exception {
* AtomicInteger source = new AtomicInteger();
*
* ExecResult<Integer> result = ExecHarness.yieldSingle(exec ->
* Promise.sync(source::incrementAndGet)
* .mapIf(i -> i < 3, i -> { throw new IllegalStateException(); })
* .retry(3, Duration.ofMillis(500), (i, t) -> LOG.add("retry attempt: " + i))
* );
*
* assertEquals(Integer.valueOf(3), result.getValue());
* assertEquals(Arrays.asList("retry attempt: 1", "retry attempt: 2"), LOG);
* }
* }
* }</pre>
*
* @param maxAttempts the maximum number of times to retry
* @param delay the duration to wait between retry attempts
* @param onError the error handler
* @return a promise with a retry error handler
* @see #retry(int, BiFunction)
* @since 1.5
*/
default Promise<T> retry(int maxAttempts, Duration delay, @NonBlocking BiAction<? super Integer, ? super Throwable> onError) {
Promise<Duration> delayPromise = Promise.value(delay);
return retry(maxAttempts, (i, error) ->
Operation.of(() ->
onError.execute(i, error)
)
.flatMap(delayPromise)
);
}
/**
* Causes {@code this} yielding the promised value to be retried on error, after a calculated delay.
* <p>
* The given function is invoked for each failure,
* with the sequence number of the failure as the first argument and the failure exception as the second.
* It should return the duration of time to wait before retrying.
* To retry immediately, return a zero duration.
* Use {@link #retry(int, Duration, BiAction)} if desiring a fixed delay between attempts.
* <p>
* Any exception thrown by the function – possibly the exception it receives as an argument – will
* be propagated to the subscriber, yielding a failure.
* This can be used to selectively retry on certain failures, but immediately fail on others.
* <p>
* If the promise fails {@code maxAttempts} times,
* the given function will not be invoked and the most and the most recent exception will propagate.
*
* <pre class="java">{@code
* import ratpack.exec.ExecResult;
* import ratpack.exec.Promise;
* import ratpack.test.exec.ExecHarness;
*
* import java.time.Duration;
* import java.util.Arrays;
* import java.util.LinkedList;
* import java.util.List;
* import java.util.concurrent.atomic.AtomicInteger;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
* private static final List<String> LOG = new LinkedList<>();
*
* public static void main(String... args) throws Exception {
* AtomicInteger source = new AtomicInteger();
*
* ExecResult<Integer> result = ExecHarness.yieldSingle(exec ->
* Promise.sync(source::incrementAndGet)
* .mapIf(i -> i < 3, i -> { throw new IllegalStateException(); })
* .retry(3, (i, t) -> {
* LOG.add("retry attempt: " + i);
* return Promise.value(Duration.ofMillis(500 * i));
* })
* );
*
* assertEquals(Integer.valueOf(3), result.getValue());
* assertEquals(Arrays.asList("retry attempt: 1", "retry attempt: 2"), LOG);
* }
* }
* }</pre>
*
* @param maxAttempts the maximum number of times to retry
* @param onError the error handler
* @return a promise with a retry error handler
* @see #retry(int, Duration, BiAction)
* @since 1.5
*/
default Promise<T> retry(int maxAttempts, BiFunction<? super Integer, ? super Throwable, Promise<Duration>> onError) {
return transform(up -> down -> DefaultPromise.retryAttempt(1, maxAttempts, up, down, onError));
}
}