future’s success function. In either
case, the appropriate processing occurs. This allows the promise to work
as an asynchronous equivalent to a try/
Promises also have another feature, unlike their cousin, the callback:
a deferred can be resolved or rejected with a future. When this happens,
a new promise is inserted into the
chain before any futures waiting on
the current step. An important use of
this feature is recovery code in a failure function. By resolving it with a future, a failed attempt to get data can
try again without the other futures
waiting on the data knowing an error
occurred. By using this technique, developers can encapsulate portions of
a programming task into loosely coupled atomic functions whose ordering
can easily be reasoned about, as shown
in Figure 1.
States. Because promises must
run either their success function or
failure function, the overall system
maintained by the central authority
has only a few states that must be reasoned about. First, there is the waiting
future. Once a worker begins processing, the future could be left in a
waiting state forever, since its associated deferred has not been resolved.
To deal with this, the central authority
should expose a function to the worker
with which it can receive periodic notifications as a health pulse. Even using
these periodic notifications can still
have loss, however. The central authority should sweep the currently outstanding work and reject any promises that have waited too long. This will
trigger the retry code down the chain.
Once the promise gets resolved
with the results of the work, it must
start running through the future
chains attached to it. During any of
these steps, an error could occur. Luckily, promises transform thrown exceptions into calls for the next catch function. Passing these errors up the chain
means they will get properly handled in
the catch functions or passed along to
the external callers. This prevents work
from silently failing, or completing in
a bad state.
Finally, if the work concludes, ev-
erything has functioned as desired,
and the system has processed the re-
quest. Because promises abstract out
the work can be completed multiple
times without detriment, then an avail-
able and partition-tolerant algorithm
would be most appropriate. As much
as possible, no worker will duplicate
effort, but no disaster will occur in the
edge case. For work that can happen
only once, a consistent and partition-
tolerant algorithm should be used
instead for the locking mechanism.
Some workers will not be able to pro-
cess in the case of a network partition,
but that is the trade-off for consistency.
Upon completion of this system,
programmers no longer need to worry
about the details of how to synchronize. They can expend their effort instead on the problem they actually
need to solve.
What Are Promises?
Since promises provide the core capabilities used by this algorithm, it is
important to understand them. Fundamentally, a promise has two components, herein called deferreds and
futures. A deferred is the input side
of a promise, the piece where work
actually happens. Once the work is accomplished, the deferred is resolved
with the result. If an error occurs, the
deferred can be rejected with the error instead. In either case, the future
is the output side of a promise, which
receives the result. Futures can be
chained, so one future can act as the
deferred for other futures.
Every future takes two functions:
success and failure. If the deferred
gets resolved, then the success function
is called with the resulting value. If the
deferred gets rejected, then the failure
function is called with the error instead.
If a future does not have the appropriate function defined, then the value bubbles up to any attached futures.
Because futures can have an error function attached, they do not need
to have error-handling code inside the
success function. This avoids one of
the problems of callbacks: mixing error handling with successful processing code. But what if the error is recoverable? Then there is an intermediate
future with only an error function. If
an error occurs, it can fix it and then
resolve itself. This calls the next future’s success function. If no error
occurs, then the intermediate function simply passes the value to the next
This system lets
bogged down in
the details of
locking or complex