T - the type of this future. All concrete instances of this type must be serializablepublic interface FlowFuture<T> extends Serializable
CompletionStage) that allow
asynchronous computation to be chained into the current execution graph of a Flow.
All asynchronous operations start with a call to an operation on Flow - for instance to create a simple asynchronous step call :
Flow fl = Flows.currentFlow(); int var = 100; // Create a deferred asynchronous computation FlowFuture<Integer> intFuture = fl.supply(() -> { return 10 * var ;}); // Chain future computation onto the completion of the previous computation FlowFuture<String> stringFuture = intFuture.thenApply(String::valueOf);
FlowFutures are non-blocking by default - once a chained computation has been added to the current flow it will run independently of the calling thread once any dependent FlowFutures are complete (or immediately if it has no dependencies or dependent stages are already completed).
As computation is executed remotely (in the form of a captured lambda passed as an argument to a FlowFuture method), the captured context of each of the chained lambdas must be serializable. This includes any captured variables passed into the lambda.
For instance, in the above example, the variable x will be serialized and copied into the execution of the captured lambda.
Generally, if a FlowFuture completes exceptionally, it will propagate its exception to any chained dependencies, however handle(Flows.SerBiFunction),
whenComplete(Flows.SerBiConsumer) and exceptionally(Flows.SerFunction) allow errors to be caught and handled and
applyToEither(FlowFuture, Flows.SerFunction) and acceptEither(FlowFuture, Flows.SerConsumer) will only propagate errors if all
dependent futures fail with an error.
| Modifier and Type | Method and Description |
|---|---|
FlowFuture<Void> |
acceptEither(FlowFuture<? extends T> alt,
Flows.SerConsumer<T> fn)
Run an action when the first of two futures completes successfully.
|
<U> FlowFuture<U> |
applyToEither(FlowFuture<? extends T> alt,
Flows.SerFunction<T,U> fn)
Transform the outcome of the first of two futures to complete successfully.
|
boolean |
cancel()
If not already completed, completes this future exceptionally with a @{link java.util.concurrent.CancellationException}
|
boolean |
complete(T value)
If not already completed, completes this future with the given value.
|
boolean |
completeExceptionally(Throwable throwable)
If not already completed, completes this future exceptionally with the supplied
Throwable. |
FlowFuture<T> |
exceptionally(Flows.SerFunction<Throwable,? extends T> fn)
Handle exceptional completion of this future and convert exceptions to the original type of this future.
|
FlowFuture<T> |
exceptionallyCompose(Flows.SerFunction<Throwable,FlowFuture<T>> fn)
Compose a new computation on to the completion of this future to handle errors,
|
T |
get()
Get the result of this future
|
T |
get(long timeout,
TimeUnit unit)
Get the result of this future, indicating not to wait over the specified timeout.
|
T |
getNow(T valueIfAbsent)
Get the result of this future if completed or the provided value if absent.
|
<X> FlowFuture<X> |
handle(Flows.SerBiFunction<? super T,Throwable,? extends X> fn)
Invoke a handler when a computation is complete, regardless of whether it completed successfully or with an error -
optionally transforming the resulting value or error to a new value.
|
FlowFuture<Void> |
thenAccept(Flows.SerConsumer<T> fn)
Perform an action when this future completes successfully.
|
<U> FlowFuture<Void> |
thenAcceptBoth(FlowFuture<U> other,
Flows.SerBiConsumer<T,U> fn)
Perform an action when this and another future have both completed normally:
|
<X> FlowFuture<X> |
thenApply(Flows.SerFunction<T,X> fn)
Applies a transformation to the successfully completed value of this future.
|
<U,X> FlowFuture<X> |
thenCombine(FlowFuture<? extends U> other,
Flows.SerBiFunction<? super T,? super U,? extends X> fn)
Combine the result of this future and another future into a single value when both have completed normally:
|
<X> FlowFuture<X> |
thenCompose(Flows.SerFunction<T,FlowFuture<X>> fn)
Compose a new computation on to the completion of this future
|
FlowFuture<Void> |
thenRun(Flows.SerRunnable fn)
Run an action when this future has completed normally.
|
FlowFuture<T> |
whenComplete(Flows.SerBiConsumer<T,Throwable> fn)
Perform an action when a computation is complete, regardless of whether it completed successfully or with an error.
|
<X> FlowFuture<X> thenApply(Flows.SerFunction<T,X> fn)
This method allows simple sequential chaining of functions together - each stage may be executed independently in a different environment.
Flow fl = Flows.currentFlow(); fl.supply(() -> { })
X - the returning type of the functionfn - a serializable function to perform once this future is completefnCompletionStage.thenApply(java.util.function.Function)<X> FlowFuture<X> thenCompose(Flows.SerFunction<T,FlowFuture<X>> fn)
thenCompose allows you to dynamically compose one or more steps onto a computation dependent on the value
of a previous step while treating the result of those steps as a single future.
For instance you may create tail-recursive loops or retrying operations with this call:
If the lambda returns null then the compose stage will fail with aprivate static FlowFuture<String> doSomethingWithRetry(String input, int attemptsLeft) { Flow fl = Flows.currentFlow(); try{ // some computation that may thrown an error String result = someUnreliableComputation(input); return fl.completedValue(result); }catch(Exception e){ if(attemptsLeft > 0){ // delay and retry the computation later. return rt.delay(5000) .thenCompose((ignored) -> doSomethingWithRetry(input, attemptsLeft - 1)); }else{ throw new RuntimeException("Could not do unreliable computation", e); } } }
InvalidStageResponseException.X - the type of the future that the composed operation returnsfn - a serializable function that returns a new computation to chain after the completion of this futurefnCompletionStage.thenCompose(java.util.function.Function)FlowFuture<T> exceptionallyCompose(Flows.SerFunction<Throwable,FlowFuture<T>> fn)
exceptionallyCompose works like a combination of thenCompose(Flows.SerFunction) and exceptionally(Flows.SerFunction). This allows an error handler to compose a new computation into the graph in order to recover from errors.
For instance you may use this to re-try a call :
If the lambda returns null then the compose stage will fail with aprivate static FlowFuture<String> retryOnError(String input, int attemptsLeft) { Flow fl = Flows.currentFlow(); fl.invokeFunction("./unreliableFunction") .exceptionallyCompose((t)->{ if(isRecoverable(t) && attemptsLeft > 0){ return retryOnError(input,attemptsLeft -1); }else{ return fl.failedFuture(t); } }); }
InvalidStageResponseException.fn - a serializable function that returns a new computation to chain after the completion of this futurefnCompletionStage.thenCompose(java.util.function.Function)<U,X> FlowFuture<X> thenCombine(FlowFuture<? extends U> other, Flows.SerBiFunction<? super T,? super U,? extends X> fn)
Flow fl = Flows.currentFlow(); FlowFuture<Integer> f1 = fl.supply(() -> { int x; // some complex computation return x; }); FlowFuture<String> combinedResult = fl.supply(() -> { int y; // some complex computation return x; }).thenCombine(f1, (a, b) -> return "Result :" + a + ":" + b);
In the case that either future completes with an exception (regardless of whether the other computation succeeded) or the handler throws an exception then the returned future will complete exceptionally .
U - the type of composed futureX - the return type of the combined valueother - a future to combine with this futurefn - a function to transform the results of the two futuresfn when both this and other have
completed and fn has been run.CompletionStage.thenCombine(CompletionStage, BiFunction)FlowFuture<T> whenComplete(Flows.SerBiConsumer<T,Throwable> fn)
Tha action takes two parameters - representing either the value of the current future or an error if this or any dependent futures failed.
Only one of these two parameters will be set, with the other being null.
Flow fl = Flows.currentFlow(); FlowFuture<Integer> f1 = fl.supply(() -> { if(System.currentTimeMillis() % 2L == 0L) { throw new RuntimeException("Error in stage"); } return 100; }); f1.whenComplete((val, err) -> { if(err != null){ // an error occurred in upstream stage; }else{ // the preceding stage was successful } });
whenComplete is always called when the current stage is complete and does not change the value of current
future - to optionally change the value or to use handle(Flows.SerBiFunction)
fn - a handler to call when this stage completesCompletionStage.whenComplete(BiConsumer)boolean complete(T value)
An example is where you want to complete a future with a default value if it doesn't complete within a given time.
Flow flow = Flows.currentFlow(); FlowFuture<Integer> f = flow.supply(() -> { // some computation }); // Complete the future with a default value after a timeout flow.delay(10, TimeUnit.SECONDS) .thenAccept(v -> f.complete(1));
value - the value to assign to the futureboolean completeExceptionally(Throwable throwable)
Throwable.
An example is where you want to complete a future exceptionally with a custom exception if it doesn't complete within a given time.
Flow flow = Flows.currentFlow(); FlowFuture<Integer> f = flow.supply(() -> { // some computation }); flow.delay(10, TimeUnit.SECONDS) .thenAccept(v -> f.completeExceptionally(new ComputationTimeoutException()));
throwable - the @{link Throwable} to complete the future exceptionally withboolean cancel()
An example is where you want to cancel the execution of a future and its dependents if it doesn't complete within a given time.
Flow flow = Flows.currentFlow(); FlowFuture<Integer> f = flow.supply(() -> { // some computation }).thenAccept(x -> { // some action }); flow.delay(10, TimeUnit.SECONDS) .thenAccept(v -> f.cancel());
FlowFuture<Void> thenAccept(Flows.SerConsumer<T> fn)
This allows you to consume the successful result of a future without returning a new value to future stages.
Flow fl = Flows.currentFlow(); DataBase db; FlowFuture<Integer> f1 = fl.supply(() -> { int var; // some computation return var; }); f1.thenAccept((var) -> { db.storeValue("result", var); });
If this or a preceding future completes exceptionally then the resulting future will complete exceptionally without the handler being called.
fn - a handler to call when this future completes successfullythis has completed and fn has been run.CompletionStage.thenAccept(Consumer)FlowFuture<Void> acceptEither(FlowFuture<? extends T> alt, Flows.SerConsumer<T> fn)
Flow fl = Flows.currentFlow(); FlowFuture<Integer> f1 = fl.supply(() -> { int var; // some long-running computation return var; }); FlowFuture<Integer> f2 = fl.supply(() -> { int var; // some long-running computation return var; }); f1.acceptEither(f2, (val) -> { DataBase db = .... ; db.storeValue("result", var); });
In the case that one future completes exceptionally but the other succeeds then the successful value will be used.
When both futures complete exceptionally then the returned future will complete exceptionally with one or other of the exception values (which exception is not defined).
To transform the value and return it in the future use applyToEither(FlowFuture, Flows.SerFunction).
alt - a future to combine with this futurefn - a handler to call when the first of this and alt completes successfullyalt completes successfully or completes
exceptionally when both this and alt complete exceptionally.CompletionStage.acceptEither(CompletionStage, Consumer)<U> FlowFuture<U> applyToEither(FlowFuture<? extends T> alt, Flows.SerFunction<T,U> fn)
Flow fl = Flows.currentFlow(); FlowFuture<Integer> f1 = fl.supply(() -> { int var; // some long-running computation return var; }); FlowFuture<Integer> f2 = fl.supply(() -> { int var; // some long-running computation return var; }); f1.applyToEither(f2, (val) -> { return "Completed result: " + val; });
In the case that one future completes exceptionally but the other succeeds then the successful value will be used.
When both futures complete exceptionally then the returned future will complete exceptionally with one or other of the exception values (which exception is not defined).
To accept a value without transforming it use acceptEither(FlowFuture, Flows.SerConsumer)
U - the returned type of the transformationalt - a future to combine with this onefn - a function to transform the first result of this or alt withalt has completed and fn has
been completed successfully.CompletionStage.applyToEither(CompletionStage, Function)<U> FlowFuture<Void> thenAcceptBoth(FlowFuture<U> other, Flows.SerBiConsumer<T,U> fn)
Flow fl = Flows.currentFlow(); FlowFuture<Integer> f1 = fl.supply(() -> { int x; // some complex computation return x; }); FlowFuture<String> combinedResult = fl.supply(() -> { int y; // some complex computation return x; }).thenAcceptBoth(f1, (a, b) -> { Database db = ....; db.store("Result", a + b); });
In the case that either future completes with an exception (regardless of whether the other computation succeeded) or the handler throws an exception then the returned future will complete exceptionally.
U - the type of the composed futureother - a future to combine with this futurefn - a consumer that accepts the results of this and other when both have completedfn when both this and other have
completed and fn has been run.CompletionStage.thenAcceptBoth(CompletionStage, BiConsumer)FlowFuture<Void> thenRun(Flows.SerRunnable fn)
Flow fl = Flows.currentFlow(); FlowFuture<Integer> f1 = fl.supply(() -> { int x; // some complex computation return x; }); f1.thenRun(() -> System.err.println("computation completed"));
When this future completes exceptionally then the returned future will complete exceptionally.
fn - a runnable to run when this future has completed normallyfn has been runCompletionStage.thenRun(Runnable)<X> FlowFuture<X> handle(Flows.SerBiFunction<? super T,Throwable,? extends X> fn)
Tha action takes two parameters - representing either the value of the current future or an error if this or any dependent futures failed.
Only one of these two parameters will be set, with the other being null.
Flow fl = Flows.currentFlow(); FlowFuture<String> f1 = fl.supply(() -> { if(System.currentTimeMillis() % 2L == 0L) { throw new RuntimeException("Error in stage"); } return 100; }).handle((val, err) -> { if(err != null){ return "An error occurred in this function"; }else{ return "The result was good :" + val; } });
handle is always called when the current stage is complete and may change the value of the returned future -
if you do not need to change this value use whenComplete(Flows.SerBiConsumer)
X - The type of the transformed outputfn - a handler to call when this stage completes successfully or with an errorCompletionStage.handle(BiFunction)FlowFuture<T> exceptionally(Flows.SerFunction<Throwable,? extends T> fn)
When an exception occurs within this future (or in a dependent future) - this method allows you to trap and handle that exception (similarly to a catch block) yeilding a new value to return in place of the error.
Unlike handle(Flows.SerBiFunction) and whenComplete(Flows.SerBiConsumer) the handler
function is only called when an exception is raised, otherwise the returned future completes with the same value as
this future.
Flow fl = Flows.currentFlow(); FlowFuture<Integer> f1 = fl.supply(() -> { if(System.currentTimeMillis() % 2L == 0L) { throw new RuntimeException("Error in stage"); } return 100; }).exceptionally((err)->{ return - 1; });
fn - a handler to trap errorsfn on the exception value if it completes exceptionally.CompletionStage.exceptionally(Function)T get()
This method blocks until the current future completes successfully or with an error.
Warning: This method should be used carefully. Blocking within a fn call (triggered by a HTTP request) will result in the calling function remaining active while the underlying computation completes.
FlowCompletionException - when this future fails with an exception;T get(long timeout, TimeUnit unit) throws TimeoutException
This method blocks until either the current future completes or the given timeout period elapses, in which case it throws a TimeoutException.
Warning: This method should be used carefully. Blocking within a fn call (triggered by a HTTP request) will result in the calling function remaining active while the underlying computation completes.
timeout - the time period after which this blocking get may time outunit - the time unit of the timeout argumentFlowCompletionException - when this future fails with an exception;TimeoutException - if the future did not complete within at least the specified timeoutT getNow(T valueIfAbsent)
This method returns the result of the current future if it is complete. Otherwise, it returns the given valueIfAbsent.
valueIfAbsent - the value to return if not completedFlowCompletionException - when this future fails with an exception;Copyright © 2021. All rights reserved.