org.zodiac.sdk.async.promises.package.html Maven / Gradle / Ivy
Async Promises API
Async Promises API
Asynchronous promises use the "promise" idiom to handle the case where
the logic that does the work may be asynchronous and will not complete
synchronously. The logic of an AsyncPromise is passed a "trigger" object
which it will call when the work has either succeeded or failed.
The library makes no assumptions that it knows how to run the logic that
will be run - whatever it is simply calls the trigger when the work is
complete. So it is suitable for making calls to external asynchronus
libraries where you don't control how or on what thread the subsequent
logic will be run.
Use Case
It is not uncommon to have more than one callback-based library to wire
together, or to have a sequence of asynchronous callbacks you want to
execute, with a guarantee that they are all executed or you get notified
in the case of failure. In asynchronous libraries, it is common that they provide
the plumbing that actually runs your callback, so promises shouldn't
make an assumption that they can throw stuff in a thread pool to run
themselves.
This library can be used for synchronous promises as well - just call
the passed trigger's trigger()
method synchronously for
that.
Usage
The basic way to use this library is to implement the class
Logic or
SimpleLogic, and pass that to
the static AsyncPromise.create()
method. The Logic interface
is a functional interface which simply takes an input parameter
and a trigger and a context (see below), and does something,
which might be synchronous or asynchronous, but in either case, ends with
a call to the trigger object to provide the results and possibly trigger
the next promise to do something with that output, if running in a chain.
The logic will not actually be run until the Promise's start()
method is called (you pass it the initial input, which will be passed to your
Logic implementation).
Chaining
AsyncPromises can be chained together, and one can accept as input the output of
a previous one. The result of chaining (using AsyncPromise's then()
method is a new AsyncPromise which takes the original one's input type as input,
and outputs the output type of the Logic or AsyncPromise instance you just passed.
So, if you start with:
AsyncPromise<A,B>
and call then
on it with a new Logic or promise parameterized on B, C, you
get:
AsyncPromise<A,B> first = ...;
AsyncPromise<A,C> chained = first.then(new Logic<B, C>() { ... });
or using lambdas
AsyncPromise<A,C> chained = first.then((B data, Trigger<C> next) -> { ... });
and when you call first.start(someA)
, the first and second will be run
(you can pass an optional Trigger
object to start()
to
collect the final results).
If you want to chain together promises of heterogenous types, simply use the then()
method that takes an input argument:
AsyncPromise<A,B> first = ...;
AsyncPromise<B,C> second = ...;
AsyncPromise<Q,R> third = ...;
AsyncPromise<A,R> chained = first.then(second).then(new Q(), third);
and then to actually execute all of the logic in sequence,
A a = ...;
chained.start(a);
Promise Context
The PromiseContext provides a way for decoupled
Logic instances to communicate between themselves. It is passed to Logic's method.
Key objects - PromiseContext.Key - are used to store and retrieve data from it.
The key instance must be known to both Logics:
static Key<String> NAME_KEY = PromiseContext.key(String.class);
AsyncPromise<A,B>.create((A data, Trigger<B> trigger, PromiseContext context) -> {
String interestingString = ...;
context.put(key, interestingString);
...
});
AsyncPromise<B,C>.create((A data, Trigger<C> trigger, PromiseContext context) -> {
String interestingString = context.get(NAME_KEY);
...
});
Failure Handling
The FailureHandler lets you handle errors that occured
during asynchronous processing. So you can attach a FailureHandler to a promise, and it
will be notified in the case of failure.
When building a chain of promises, multiple failure handlers may be attached to different
steps in the promise; on failure, the failure propagates backward to the nearest handler,
which can return true
from its onFailure
method to indictate
the failure should continue propagating backward to earlier failure handlers.
A failure consists of either passing a Throwable
to the trigger, or
throwing an exception during logic execution.