holdup
v0.6.0
Published
Wrangle CommonJS Promises.
Downloads
9
Maintainers
Readme
/$$ /$$ /$$
| $$ | $$ | $$
| $$$$$$$ /$$$$$$ | $$ /$$$$$$$ /$$ /$$ /$$$$$$
| $$__ $$ /$$__ $$| $$ /$$__ $$| $$ | $$ /$$__ $$
| $$ \ $$| $$ \ $$| $$| $$ | $$| $$ | $$| $$ \ $$
| $$ | $$| $$ | $$| $$| $$ | $$| $$ | $$| $$ | $$
| $$ | $$| $$$$$$/| $$| $$$$$$$| $$$$$$/| $$$$$$$/
|__/ |__/ \______/ |__/ \_______/ \______/ | $$____/
| $$
| $$
|__/
Holdup is a small but full-featured flow-control library that makes working with promises or callbacks in Javascript easy. With Holdup you define the dependencies of your functions and it executes them for you with maximal concurrency: you don't have to explicitly state whether they're parallel or serial, or what order each one should run in. It just works.
Holdup runs in Node, Component-spec environments, and ordinary browsers; it has no dependencies and is extensively unit-tested. It works with any CommonJS Promises/A or Promises/A+ compliant promise implementation; it also provides its own Promises/A+ compliant promise implementation. It clocks in at a skinny 3.7k minified and gzipped.
Examples
Here's an example of how to define a task that depends on three other Node-style async functions:
var taskA = holdup.wrap(nodeFn);
var taskB = holdup.wrap(otherNodeFn);
var taskC = holdup.wrap(finalNodeFn, arg1, arg2);
holdup.all(taskA, taskB, taskC).then(function() {
// do work
}, function() {
// handle errors
});
In this case, taskA
, taskB
, and taskC
will all immediately execute in
parallel, and the work will wait until all three finish successfully. If any of
the dependencies fail, the error callback will be called. All of the tasks are
themselves just Promises/A+ compliant promises, so they can be used with any
library that works with Promises/A or Promises/A+.
That was a simple example, though: do some work in parallel, and when it finishes do some other work. What if we have multiple dependencies on separate parallel workloads? Holdup handles that well too:
var taskA = holdup.wrap(fnA);
var taskB = holdup.wrap(fnB);
var taskC = holdup.all(taskA, taskB).then(function() {
return holdup.wrap(fnC);
});
var taskD = holdup.all(taskA).then(function() { return holdup.wrap(fnD); });
var taskE = holdup.all(taskC, taskD).then(function() {
return holdup.wrap(fnE);
});
In the above, taskE
depends both on taskC
and taskD
. taskD
depends on
taskA
, as does taskC
-- but taskC
has an additional dependency on
taskB
. This might be a pain to hand-optimize with raw callbacks, but since
you just have to specify dependencies you can let Holdup take care of the work
for you.
Holdup understands more than success, though. Sometimes, things break; it's still important to handle those failures. It might also be important to take certain actions if enough things break, or if specific combinations of things break. With Holdup, it's easy to define those relationships:
var taskA = holdup.wrap(fnA),
taskB = holdup.wrap(fnB),
taskC = holdup.wrap(fnC),
taskD = holdup.all(taskA, taskB),
taskE = holdup.none(taskC, taskD);
taskE.then(function() {
// only gets called when taskC and taskD both error out
}, function() {
// gets called if either taskC or taskD pass, or if they both do
});
Similarly, Holdup has a resolved
function that fulfills once everything has
resolved -- even if everything resolved to an error state, or if some resolved
one way and others resolved another. It passes the fulfilled and rejected
promises in separate arrays to the then
callback, as so:
var taskA = holdup.wrap(fnA),
taskB = holdup.wrap(fnB),
taskC = holdup.wrap(fnC),
taskD = holdup.resolved(taskA, taskB, taskC);
taskD.then(function(fulfilled, rejected) {
// this is called once taskA, taskB, and taskC are no longer in pending
// states.
// fulfilled is an array of which tasks have fulfilled, and rejected is an
// array of which tasks have rejected.
});
The all
, none
, and resolved
functions form the basis of Holdup's API, but
they're not the only ones available. Holdup provides a full-featured functional
API for working with promises, wrapping Node-style functions, and easily making
your own promises. Keep reading for the full API documentation.
API
Promise Methods
Promises created by Holdup are Promises/A+ compliant and expose the following methods:
promise.then(onFulfilled, onRejected)
Given optional callbacks to be called on promise fulfillment or rejection,
returns a new promise that will adopt the state of the onFulfilled
callback
if promise
fulfills, or will adopt the state of promise
otherwise.
promise.error(onRejected)
An alias for promise.then(null, onRejected)
.
promise.error(ErrorClass, onRejected)
Similar to a call to promise.error(onRejected)
, except that onRejected
will
only be called if the given rejection reason is an instance of ErrorClass
.
promise.thrown(onThrown)
:warning: Warning: promise.thrown
is not interoperable with other
Promises/A+ spec libraries; the Promises/A+ spec doesn't make it possible in a
general case to tell whether a promise has rejected due to a thrown error, or
whether it rejected for a different reason. If you need the functionality of
promise.thrown
— for example, if you're running in Node and follow the Node
core team's recommendation to shutdown on thrown errors to avoid memory
leaks — it's recommended that you not use Holdup in conjunction with
other Promise libraries.
Similar to a call to promise.then(null, onThrown)
, except that onThrown
will be called only if the promise rejects due to a thrown error.
promise.thrown(ThrownClass, onThrown)
Similar to a call to promise.thrown(onThrown)
, except that onThrown
will be
called only if the given rejection reason is an instance of ThrownClass
.
Creating Promises
holdup.make(callback)
Given a callback of form function(fulfill, reject) {}
, returns a promise that
will be fulfilled when the callback calls fulfill
or rejected when the
promise calls reject
.
The returned promise will call its then
callback with whatever is passed to
the fulfill
callback, and will call its then
errback with whatever is
passed to the reject
errback.
holdup.fulfill(value)
Given a value, returns a promise that will fulfill to the value.
Essentially a degenerate, but convenient form of holdup.make
for creating
promises that you know will fulfill to a specific value.
holdup.fcall(fn)
Given a function that returns a value, returns a promise that will fulfill to the result of the given function.
Essentially a degenerate, but convenient form of holdup.make
for creating
promises that you know will fulfill to a specific value.
holdup.reject(fn)
Given a reason, returns a promise that will reject with the reason.
Essentially a degenerate but convenient form of holdup.make
for creating
promises that you know will reject to a specific value.
holdup.ferr(fn)
Given a function that returns a value, returns a promise that will reject with the result of the given function passed as the rejection reason.
Essentially a degenerate but convenient form of holdup.make
for creating
promises that you know will reject to a specific value.
Manipulating Promises
holdup.all
Takes an arg list, array, array of arrays, arg lists of arrays... etc containing promises.
Returns a promise that will be fulfilled if all the promises fulfill, and will reject as soon as any of the promises reject.
It will call its then
callback with the array of all fulfilled values, in the
order that their respective promises were passed in. It will call its then
errback with the first promise to reject.
holdup.none
Takes an arg list, array, array of arrays, arg list of arrays... etc containing promises.
Returns a promise that will be fulfilled if all of the promises reject, and will reject as soon as any of the promises fulfill.
The returned promise will call its then
callback with the array of all
rejected promises, in the order that they rejected. It will call its then
errback with the first promise to fulfill.
holdup.settled
alias: holdup.resolved
, holdup.allSettled
Takes an arg list, array, array of arrays, arg list of arrays... etc containing promises.
Returns a promise that will be fulfilled once all of the given promises are no longer in a pending state; i.e., once they've each been rejected or fulfilled. The promises don't have to end in the same state: they only have to leave the pending state. The returned promise will never reject.
The returned promise will call its then
callback with an array of promise
Inspection instances, with each inspection in the order that its respective
promise was passed in.
holdup.firstValue
alias: holdup.race
Takes an arg list, array, array of arrays, arg list of arrays... etc containing promises.
Returns a promise that will be fulfilled as soon as the first of the given promises fulfills, and will reject if none of the promises fulfill.
The returned promise will call its then
callback with the first fulfilled
value, and will call its then
errback with the array of all rejected errors
in the order that their respective promises were passed in.
holdup.firstError
Takes an arg list, array, array of arrays, arg list of arrays... etc containing promises.
Returns a promise that will be fulfilled as soon as the first of the given promises rejects, and will reject if none of the promises reject.
The returned promise will call its then
callback with the first rejection
reason, and will call its then
errback with the array of all fulfilled values
in the order that their respective promises were passed in.
holdup.lastValue
Takes an arg list, array, array of arrays, arg list of arrays... etc containing promises.
Returns a promise that will be fulfilled once all of the promises have left their pending state, and at least one has fulfilled. It will reject if all given promises reject.
The returned promise will call its then
callback with the value of the last
fulfilled promise, and will call its then
errback with the array of all
rejection reasons in the order that the respective promises were passed in.
holdup.lastError
Takes an arg list, array, array of arrays, arg list of arrays... etc containing promises.
Returns a promise that will be fulfilled once all of the promises have left their pending state, and at least one has rejected. It will reject if all given promises fulfill.
The returned promise will call its then
callback with the rejection reason
of the last rejected promise, and will call its then
errback with the array
of all fulfilled promise values in the order that their respective promises
were passed in.
holdup.invert(promise)
Given a promise, returns a promise that will reject when the given promise fulfills and will fulfill when the given promise rejects.
If data is passed to the callback of the given promise, it will be passed as the error to the returned promise's errback. If an error is passed to the errback of the given promise, it will be passed as the data to the returned promises callback.
Working With Values
holdup.spread
alias: holdup.spreadValues
Given a promise and a callback, calls the callback by applying the fulfilled value of the promise to the callback.
For example:
var a = holdup.fulfill(10),
b = holdup.fulfill(11),
c = holdup.all(a, b);
holdup.spread(c, function(aValue, bValue) {
// aValue is 10
// bValue is 11
});
holdup.spreadErrors
Given a promise and a callback, calls the callback by applying the rejected error of the promise to the callback.
For example:
var a = holdup.reject(['nope', 'definitely not']),
holdup.spreadErrors(a, function(firstError, secondError) {
// firstError is 'nope'
// secondError is 'definitely not'
});
Timing Functions
holdup.wait(milliseconds)
Given a time in milliseconds, returns a promise that calls its then
callback
after that amount of time. The returned promise will never call any errback
functions given to it.
The returned promise will pass along the given timeout interval to the then
callback as its first parameter.
holdup.delay(promise, milliseconds)
Given a promise and a time in milliseconds, returns a promise that fulfills when the given promise fulfills or rejects when the first one rejects, but waits the given time before fulfilling or rejecting.
holdup.timeout(promise, milliseconds)
Given a promise and a time in milliseconds, returns a promise that fulfills if the given promise fulfills before the time is up and rejects otherwise.
Integrating With Node-style Callback APIs
holdup.napply(scope, nodeFn, args)
Given a scope, a Node-style async function, and an array of arguments, returns a promise that fulfills if the given function completes successfully and rejects if it doesn't.
The returned promise will call its then
callback with anything passed as the
data
parameter to the async function (if anything is in fact passed), and
will call its then
errback with anything passed as the err
param to the
async function.
holdup.nfapply(nodeFn, args)
A convenient, scopeless version of napply
, for times when it's acceptable
that the scope of napply
be null
.
holdup.ncall(scope, nodeFn, args...)
alias: holdup.wrapFor
Given a scope, a Node-style async function, and optional arguments, returns a promise that fulfills if the given function completes successfully and rejects if it doesn't.
The returned promise will call its then
callback with anything passed as the
data
parameter to the async function (if anything is in fact passed), and
will call its then
errback with anything passed as the err
param to the
async function.
holdup.nfcall
alias: holdup.wrap
A convenient, scopeless version of ncall
, for times when it's acceptable that
the scope of ncall
be null
.
holdup.npost(obj, methodName, args)
Given an object, a method name corresponding to a Node-style async function, and an array of arguments, returns a promise that fulfills if the given method completes successfully and rejects if it doesn't.
The returned promise will call its then
callback with anything passed as the
data
parameter to the async function (if anything is in fact passed), and
will call its then
errback with anything passed as the err
param to the
async function.
holdup.ninvoke(obj, methodName, args...)
alias: holdup.send
Given an object, a method name corresponding to a Node-style async function, and an optional argument list of parameters, returns a promise that fulfills if the given method completes successfully and rejects if it doesn't.
The returned promise will call its then
callback with anything passed as the
data
parameter to the async function (if anything is in fact passed), and
will call its then
errback with anything passed as the err
param to the
async function.
holdup.nbind(nodeFn, scope, args...)
Given a Node-style async function, a scope, and an optional argument list of parameters, returns a promise-returning function bound to the scope and the given parameters.
The returned promise will call its then
callback with anything passed as the
data
parameter to the async function (if anything is in fact passed), and
will call its then
errback with anything passed as the err
param to the
async function.
For example:
var readProust = holdup.bind(fs.readFile, fs, 'proust.txt', 'utf-8');
readProust().then(function(text) {
// do things with text
});
holdup.nfbind(nodeFn, args...)
alias: holdup.denodeify
A convenient, scopeless version of nbind, for times when it's acceptable that
the scope of nbind
be null
.
holdup.nodeify(promise, callback)
Given a promise and a Node-style callback, calls the callback with the correct
data
and err
arguments when the promise fulfills or rejects. Useful for
creating dual promise/callback APIs, or for using promises internally but
exposing only a callback API.
Handling Uncaught Errors
The Promises/A+ spec dictates that errors should never "leak" synchronously out
of a promise: if you reject or throw an error, you'll never know unless you've
attached an error handler (with .then
, or the Holdup-specific .error
or
.thrown
methods).
Since often you'll want to take some action if an error occurs and is never caught (for example, report it to your error logger, or gracefully restart your server), Holdup provides events that fire when uncaught errors occur.
holdup.on('error', fn)
Calls fn
if an uncaught rejection or thrown error occurs.
holdup.on('thrown', fn)
Calls fn
if an uncaught thrown error occurs. Note that "catching" the error
by handling it with an onRejected
callback isn't enough: to prevent this from
firing, you must use a .thrown
callback. Like .thrown
and other
thrown-error-specific code in Holdup, this is not compatible with other promise
libraries.
holdup.off(type, fn)
Removes the given callback from listening to the type ('error'
or 'thrown'
)
of event.
holdup.once(type, fn)
Calls holdup.on(type, fn)
, and then after the callback fires calls
holdup.off(type, fn)
.
License
Copyright 2013-2014 Matt Baker. Licensed under the MIT License: see LICENSE.txt for details.