ratpack.exec.Promise Maven / Gradle / Ivy
/*
* 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 com.google.common.cache.LoadingCache;
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.
*
* 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.
*
* 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}.
*
* The promise is not “activated” until the {@link #then(Action)} method is called.
* This method terminates the pipeline, and receives the final value.
*
* 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 the type of promised value
*/
@SuppressWarnings("JavadocReference")
public interface Promise {
/**
* Creates a promise for value that will be produced asynchronously.
*
* 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.
*
*
{@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.async(down ->
* new Thread(() -> {
* down.success("foo");
* }).start()
* )
* ).getValueOrThrow();
*
* assertEquals(value, "foo");
* }
* }
* }
*
* @param upstream the producer of the value
* @param 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 Promise async(Upstream upstream) {
return new DefaultPromise<>(DefaultExecution.upstream(upstream));
}
/**
* Creates a promise for value, synchronously, produced by the given factory.
*
* 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.
*
*
{@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");
* }
* }
* }
*
*
* 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 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 Promise sync(Factory factory) {
return async(down -> {
T t;
try {
t = factory.create();
} catch (Exception e) {
down.error(e);
return;
}
down.success(t);
});
}
/**
* Creates a promise for the given item.
*
* The given item will be used every time that the value is requested.
*
*
{@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");
* }
* }
* }
*
* @param t the promised value
* @param the type of promised value
* @return a promise for the given item
* @see #async(Upstream)
* @see #sync(Factory)
* @see #error(Throwable)
*/
static Promise value(T t) {
return async(down -> down.success(t));
}
/**
* Creates a failed promise with the given error.
*
* The given error will be used every time that the value is requested.
*
*
{@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);
* }
* }
* }
*
* @param t the error
* @param the type of promised value
* @return a failed promise
* @see #async(Upstream)
* @see #sync(Factory)
* @see #value(Object)
*/
static Promise error(Throwable t) {
return async(down -> down.error(t));
}
/**
* Deprecated.
*
* @param upstream the producer of the value
* @param the type of promised value
* @return a promise for the asynchronously created value
* @deprecated replaced by {@link #async(Upstream)}
*/
@Deprecated
static Promise of(Upstream upstream) {
return async(upstream);
}
/**
* Deprecated.
*
* @param factory the producer of the value
* @param the type of promised value
* @return a promise for the result of the factory
* @deprecated replaced by {@link #sync(Factory)}}
*/
@Deprecated
static Promise ofLazy(Factory factory) {
return sync(factory);
}
/**
* Specifies what should be done with the promised object when it becomes available.
*
* Important: 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.
*
* 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.
*
* 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.
*
{@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 result = ExecHarness.yieldSingle(c ->
* Promise.value("foo")
* .transform(up -> down ->
* up.connect(down.onSuccess(value -> {
* try {
* down.success(value.toUpperCase());
* } catch (Throwable e) {
* down.error(e);
* }
* }))
* )
* );
*
* assertEquals("FOO", result.getValue());
* }
* }
* }
*
* 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.
*
* 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 the type of item emitted by the transformed upstream
* @return a new promise
*/
Promise transform(Function super Upstream extends T>, ? extends Upstream> upstreamTransformer);
/**
* Specifies the action to take if the an error occurs trying to produce the promised value, that the given predicate applies to.
*
* 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 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.
*
* {@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");
* });
* }
* }
* }
*
* 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 the type of exception to handle with the given action
* @return A promise for the successful result
* @since 1.1
*/
default Promise onError(Class 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.
*
* 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 onError(Action super Throwable> errorHandler) {
return onError(Predicate.TRUE, errorHandler);
}
/**
* Consume the promised value as a {@link Result}.
*
* 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> resultHandler) {
connect(new Downstream() {
@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.error(throwable)));
} catch (Throwable e) {
DefaultPromise.throwError(e);
}
}
@Override
public void complete() {
try {
resultHandler.execute(ExecResult.complete());
} catch (Throwable e) {
DefaultPromise.throwError(e);
}
}
});
}
/**
* Transforms the promised value by applying the given function to it.
* {@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 result = ExecHarness.yieldSingle(c ->
* Promise.value("foo")
* .map(String::toUpperCase)
* .map(s -> s + "-BAR")
* );
*
* assertEquals("FOO-BAR", result.getValue());
* }
* }
* }
*
* @param transformer the transformation to apply to the promised value
* @param the type of the transformed object
* @return a promise for the transformed value
*/
default Promise map(Function super T, ? extends O> transformer) {
return transform(up -> down -> up.connect(
down.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.
*
* {@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 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());
* }
* }
* }
*
* @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 mapIf(Predicate super T> predicate, Function super T, ? extends T> transformer) {
return map(t -> predicate.apply(t) ? transformer.apply(t) : t);
}
/**
* Like {@link #map(Function)}, but performs the transformation on a blocking thread.
*
* 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 the type of the transformed object
* @return a promise for the transformed value
*/
default Promise 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.
*
* 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 blockingOp(Action super T> action) {
return flatMap(t -> Blocking.op(action.curry(t)).map(() -> t));
}
/**
* Deprecated.
*
* Use {@link #replace(Promise)}.
*
* @param next the promise to replace {@code this} with
* @param 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 Promise next(Promise next) {
return flatMap(in -> next);
}
/**
* Executes the provided, potentially asynchronous, {@link Action} with the promised value as input.
*
* 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.
*
* The given action is executed within an {@link Operation}, allowing it to perform asynchronous work.
*
*
{@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 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);
* }
* }
* }
*
* @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 next(@NonBlocking Action super T> action) {
return nextOp(v ->
Operation.of(() ->
action.execute(v)
)
);
}
/**
* Executes the operation returned by the given function.
*
* This method can be used when needing to perform an operation returned by another object, based on the promised value.
*
*
{@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 values) {
* return Operation.of(() -> values.add(value.toUpperCase()));
* }
* }
*
* public static void main(String... args) throws Exception {
* CaseService service = new CaseService();
* List events = Lists.newLinkedList();
*
* ExecHarness.runSingle(c ->
* Promise.value("foo")
* .nextOp(v -> service.toUpper(v, events))
* .then(events::add)
* );
*
* assertEquals(Arrays.asList("FOO", "foo"), events);
* }
* }
* }
*
* @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 nextOp(Function super T, ? extends Operation> function) {
return transform(up -> down -> up.connect(
down.onSuccess(value ->
function.apply(value)
.onError(down::error)
.then(() ->
down.success(value)
)
)
)
);
}
/**
* Replaces {@code this} promise with the provided promise for downstream subscribers.
*
* 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.
*
* If the upstream promise fails, its error will propagate downstream and the given promise will never be subscribed to.
*
*
{@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 result = ExecHarness.yieldSingle(c ->
* Promise.value("foo")
* .next(v -> value = v)
* .replace(Promise.value("bar"))
* );
*
* assertEquals("bar", result.getValue());
* assertEquals("foo", value);
* }
* }
* }
*
* @param next the promise to replace {@code this} with
* @param the type of the value of the replacement promise
* @return a promise
* @since 1.1
*/
default Promise replace(Promise next) {
return flatMap(in -> next);
}
/**
* Transforms the promised value to a {@link Pair}, with the value of the given promise as the {@code left}.
*
* The existing promised value will become the {@code right}.
*
* @param left a promise for the left value of the result pair
* @param the type of the left value
* @return a promise
*/
default Promise> left(Promise left) {
return flatLeft(t -> left);
}
/**
* Transforms the promised value to a {@link Pair}, with the result of the given function as the {@code left}.
*
* 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 the type of the left value
* @return a promise
* @since 1.4
*/
default Promise> 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}.
*
* 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 the type of the left value
* @return a promise
* @since 1.4
*/
default Promise> flatLeft(Function super T, ? extends Promise> 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}.
*
* The existing promised value will become the {@code left}.
*
* @param right a promise for the right value of the result pair
* @param the type of the right value
* @return a promise
*/
default Promise> right(Promise right) {
return flatRight(t -> right);
}
/**
* Transforms the promised value to a {@link Pair}, with the result of the given function as the {@code right}.
*
* 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 the type of the left value
* @return a promise
* @since 1.4
*/
default Promise> 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}.
*
* 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 the type of the left value
* @return a promise
* @since 1.4
*/
default Promise> flatRight(Function super T, ? extends Promise> 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.
*
* If the function returns a value, the promise will now be considered successful.
*
{@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 result = ExecHarness.yieldSingle(c ->
* Promise.error(new Exception("!"))
* .mapError(e -> "value")
* );
*
* assertEquals("value", result.getValue());
* }
* }
* }
*
* If the function throws an exception, that exception will now represent the promise failure.
*
{@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 result = ExecHarness.yieldSingle(c ->
* Promise.error(new Exception("!"))
* .mapError(e -> { throw new RuntimeException("mapped", e); })
* );
*
* assertEquals("mapped", result.getThrowable().getMessage());
* }
* }
* }
*
* 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 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.
*
* 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 Promise mapError(Class type, Function super Throwable, ? extends T> function) {
return transform(up -> down ->
up.connect(down.onError(throwable -> {
if (type.isInstance(throwable)) {
T transformed;
try {
transformed = function.apply(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.
*
* 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 flatMapError(Function super Throwable, ? extends Promise> function) {
return transform(up -> down ->
up.connect(down.onError(throwable -> {
Promise 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.
*
* 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 Promise flatMapError(Class type, Function super E, ? extends Promise> function) {
return transform(up -> down ->
up.connect(down.onError(throwable -> {
if (type.isInstance(throwable)) {
Promise 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.
*
* This method can be used to apply custom operations without breaking the “code flow”.
* It works particularly well with method references.
*
{@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 dubble(Promise input) {
* return input.map(i -> i * 2);
* }
*
* public static Promise triple(Promise input) {
* return input.map(i -> i * 3);
* }
* }
* }
*
* If the apply function throws an exception, the returned promise will fail.
*
{@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 explode(Promise input) throws Exception {
* throw new Exception("bang!");
* }
* }
* }
*
* If the promise having the operation applied to fails, the operation will not be applied.
*
{@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.error(new Exception("bang!"))
* .apply(Example::dubble)
* ).getThrowable();
*
* assertEquals("bang!", error.getMessage());
* }
*
* public static Promise dubble(Promise input) {
* return input.map(i -> i * 2);
* }
* }
* }
*
* @param the type of promised object after the operation
* @param function the operation implementation
* @return the transformed promise
*/
default Promise apply(Function super Promise, ? extends Promise> function) {
try {
return function.apply(this);
} catch (Throwable e) {
return Promise.error(e);
}
}
/**
* Applies the given function to {@code this} and returns the result.
*
* 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.
*
{@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 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);
* }
* }
* }
*
* The given function is executed immediately.
*
* 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 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 to(Function super Promise, ? 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.
*
* This is useful when the transformation involves an asynchronous operation.
*
{@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 result = ExecHarness.yieldSingle(c ->
* Promise.value("foo")
* .flatMap(s -> Blocking.get(s::toUpperCase))
* .map(s -> s + "-BAR")
* );
*
* assertEquals("FOO-BAR", result.getValue());
* }
* }
* }
*
*
* @param transformer the transformation to apply to the promised value
* @param the type of the transformed object
* @return a promise for the transformed value
*/
default Promise flatMap(Function super T, ? extends Promise> transformer) {
return transform(up -> down ->
up.connect(down.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.
*
* {@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 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());
* }
* }
* }
*
* @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 flatMapIf(Predicate super T> predicate, Function super T, ? extends Promise> transformer) {
return flatMap(t -> predicate.apply(t) ? transformer.apply(t) : Promise.value(t));
}
/**
* Allows the promised value to be handled specially if it meets the given predicate, instead of being handled by the promise subscriber.
*
* This is typically used for validating values, centrally.
*
{@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 yield(int i, List 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 routed = Lists.newLinkedList();
*
* ExecResult 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 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.
*
{@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 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());
* });
* }
* }
* }
*
* 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 route(Predicate super T> predicate, Action super T> action) {
return transform(up -> down ->
up.connect(down.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.
*
* 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 onNull(Block action) {
return route(Objects::isNull, ignoreArg(action));
}
/**
* Caches the promised value (or error) and returns it to all subscribers.
*
* This method is equivalent to using {@link #cacheResultIf(Predicate)} with a predicate that always returns {@code true}.
*
*
{@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 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 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()));
* });
* }
* }
* }
*
*
* If the cached promise fails, the same exception will be returned every time.
*
*
{@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
*
* @return a caching promise
* @see #cacheIf(Predicate)
* @see #cacheResultIf(Predicate)
*/
default Promise cache() {
return cacheResultIf(Predicate.TRUE);
}
/**
* Caches the promise value and provides it to all future subscribers, if it satisfies the predicate.
*
* 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.
*
* Non success results will not be cached.
*
* @param shouldCache the test for whether a successful result is cacheable
* @return a caching promise
* @since 1.4
*/
default Promise cacheIf(Predicate super T> shouldCache) {
return cacheResultIf(r -> r.isSuccess() && shouldCache.apply(r.getValue()));
}
/**
* Caches the promise result and provides it to all future subscribers, if it satisfies the predicate.
*
* This method is typically used when wanting to cache a failure result or a success result.
* Moreover, the error throwable or success value can be inspected to determine whether it should be cached.
*
* A cached promise is fully threadsafe and and can be subscribed to concurrently.
* While there is no cached value, yielding the upstream value is serialised.
* That is, one value is requested at a time regardless of concurrent subscription.
* If a cache-able value is received, all pending subscribers will received the cache-able value.
* If a received value is not cache-able the corresponding subscriber will receive the value,
* and the upstream promise will be subscribed to again on behalf of the next subscriber.
*
*
{@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> results = new ArrayList<>();
* AtomicInteger counter = new AtomicInteger();
* Promise 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 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);
* }
* }
* }
*
*
* Note, the cached value never expires.
* If you wish to cache a value only for a certain amount of time,
* use a general caching tool such as Guava's {@link LoadingCache}.
*
*
{@code
* import com.google.common.cache.CacheBuilder;
* import com.google.common.cache.CacheLoader;
* import com.google.common.cache.LoadingCache;
* 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.TimeUnit;
* import java.util.concurrent.atomic.AtomicInteger;
*
* import static org.junit.Assert.assertEquals;
*
* public class Example {
*
* public static void main(String... args) throws Exception {
* List> results = new ArrayList<>();
* AtomicInteger counter = new AtomicInteger();
* Promise promise = Promise.sync(counter::getAndIncrement);
*
* LoadingCache> cache = CacheBuilder.newBuilder()
* .expireAfterWrite(2, TimeUnit.SECONDS)
* .build(new CacheLoader>() {
* public Promise load(String key) throws Exception {
* return promise.cacheResultIf(i -> i.isSuccess() && i.getValue() > 1);
* }
* });
*
* ExecHarness.runSingle(e -> {
* for (int i = 0; i < 4; i++) {
* cache.get("key").result(results::add);
* }
*
* // let the cache entry expire
* Thread.sleep(2000);
*
* for (int i = 0; i < 2; i++) {
* cache.get("key").result(results::add);
* }
* });
*
* assertEquals(results.get(0).getValueOrThrow(), Integer.valueOf(0));
* assertEquals(results.get(1).getValueOrThrow(), Integer.valueOf(1));
* assertEquals(results.get(2).getValueOrThrow(), Integer.valueOf(2));
* assertEquals(results.get(3).getValueOrThrow(), Integer.valueOf(2));
*
* // cache entry has expired
*
* assertEquals(results.get(4).getValueOrThrow(), Integer.valueOf(3));
* assertEquals(results.get(5).getValueOrThrow(), Integer.valueOf(3));
* }
* }
* }
*
* @param shouldCache the test for whether a result is cacheable
* @return a caching promise
* @since 1.4
*/
default Promise cacheResultIf(Predicate super ExecResult> shouldCache) {
return transform(up -> new CachingUpstream<>(up, shouldCache));
}
/**
* Allows the execution of the promise to be deferred to a later time.
*
* 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.
*
* 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 defer(Action super Runnable> releaser) {
return transform(up -> down ->
Promise.async(innerDown ->
releaser.execute((Runnable) () -> innerDown.success(true))
).then(v ->
up.connect(down)
)
);
}
/**
* Registers a listener that is invoked when {@code this} promise is initiated.
* {@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 events = Lists.newLinkedList();
* ExecHarness.runSingle(c ->
* Promise.sync(() -> {
* events.add("promise");
* return "foo";
* })
* .onYield(() -> events.add("onYield"))
* .then(v -> events.add("then"))
* );
* assertEquals(Arrays.asList("onYield", "promise", "then"), events);
* }
* }
* }
*
* @param onYield the action to take when the promise is initiated
* @return effectively, {@code this} promise
*/
default Promise 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.
* {@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 events = Lists.newLinkedList();
* ExecHarness.runSingle(c ->
* Promise.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);
* }
* }
* }
*
* @param listener the result listener
* @return effectively, {@code this} promise
*/
default Promise wiretap(Action super Result> listener) {
return transform(up -> down ->
up.connect(new Downstream() {
@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.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}.
*
* Throttling can be used to limit concurrency.
* Typically to limit concurrent use of an external resource, such as a HTTP API.
*
* Note that the {@link Throttle} instance given defines the actual throttling semantics.
*
{@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 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);
* }
* }
* }
*
* @param throttle the particular throttle to use to throttle the operation
* @return the throttled promise
*/
default Promise throttled(Throttle throttle) {
return throttle.throttle(this);
}
/**
* Closes the given closeable when the value or error propagates to this point.
*
* This can be used to simulate a try/finally synchronous construct.
* It is typically used to close some resource after an asynchronous operation.
*
*
{@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 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!");
* });
* });
*
* }
* }
* }
*
* 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
* @since 1.3
*/
default Promise close(AutoCloseable closeable) {
return transform(up -> down ->
up.connect(new Downstream() {
@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();
}
})
);
}
/**
* Emits the time taken from when the promise is subscribed to to when the result is available.
*
* The given {@code action} is called regardless of whether the promise is successful or not.
*
* 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
* @return effectively {@code this}
*/
default Promise time(Action super Duration> action) {
return transform(up -> down -> {
long start = System.nanoTime();
up.connect(new Downstream() {
private Duration duration() {
// protect against clock skew causing negative durations
return Duration.ofNanos(Math.max(0, System.nanoTime() - start));
}
@Override
public void success(T value) {
try {
action.execute(duration());
} catch (Throwable t) {
down.error(t);
return;
}
down.success(value);
}
@Override
public void error(Throwable throwable) {
try {
action.execute(duration());
} catch (Throwable t) {
throwable.addSuppressed(t);
}
down.error(throwable);
}
@Override
public void complete() {
try {
action.execute(duration());
} catch (Throwable t) {
down.error(t);
return;
}
down.complete();
}
});
});
}
/**
* Forks a new execution and subscribes to this promise, returning a promise for its value.
*
* 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.
*
* This method can be used for simple of processing.
* It is often combined with the {@link #left(Promise)} or {@link #right(Promise)}.
*
*
{@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 result = ExecHarness.yieldSingle(r -> {
* Promise p1 = Blocking.get(() -> {
* barrier.await();
* return 1;
* });
* Promise p2 = Blocking.get(() -> {
* barrier.await();
* return "2";
* });
*
* return p1.right(p2.fork());
* }).getValueOrThrow();
*
* assertEquals(result, Pair.of(1, "2"));
* }
*
* }
* }
*
* @param execSpec configuration for the forked execution
* @return a promise
* @throws Exception any thrown by {@code execSpec}
* @since 1.4
*/
default Promise fork(Action super ExecSpec> execSpec) throws Exception {
Promised 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.
*
* This method delegates to {@link #fork(Action)} with {@link Action#noop()}.
*
* @return a promise
* @since 1.4
* @see #fork(Action)
*/
default Promise fork() {
return Exceptions.uncheck(() -> fork(Action.noop()));
}
static Promise wrap(Factory extends Promise> factory) {
try {
return factory.create();
} catch (Exception e) {
return Promise.error(e);
}
}
}