pacta
{ 'pacta': '0.5.1' }
$ npm install pacta
$ bower install pacta
This is an implementation of algebraic, Promises/A+
compliant Promises in JavaScript (both for the browser and
node.js).
Promises can be thought of as objects representing a value that may not have
been calculated yet (they are sometimes referred to as Deferred
s).
An obvious example is the result of an asynchronous HTTP request: it's not
clear when the request will be fulfilled but it will be at some point in the
future. Having actual Promise objects representing these eventual values
allows you to compose, transform and act on them without worrying about their
time or sequence of execution.
At their most basic, an empty promise can be created and resolved like so:
var Promise = require('pacta');
var p = new Promise();
setTimeout(function () {
p.resolve(1);
}, 1000);
Promises can also be marked as rejected
(viz. represent an error state) like
so:
p.reject('The server could not be found.');
Concretely, a promise can be represented by the following deterministic finite automaton:
For a worked example of using promises, see the
two
example
programs
and sample HTTP
client
included in Pacta.
Pacta's promises can be used as the following algebraic structures as defined
in the Fantasy Land
Specification:
Pacta's promises are compliant with the Promises/A+
specification, providing
a then
method.
Promises are resolved (or fulfilled) with
Promise#resolve
and rejected with
Promise#reject
.
To execute code on rejection without using
Promise#then
, use
Promise#onRejected
.
Pacta also provides the following functions for creating and working with
Promises of lists:
Promise#conjoin
to concatenate promises into a list of
values regardless of their original type meaning that non-Monoid types can
be combined with others (e.g. a promise of 'foo'
can be conjoined with
[1, 2]
to produce ['foo', 1, 2]
);Promise#append
to append promises to an initial promise
of a list. This means that you can work more easily with multiple promises
of lists without joining them together (as would be done with concat
and
conjoin
), e.g. appending a promise of [2, 3]
to a promise of [1]
results in [1, [2, 3]]
rather than [1, 2, 3]
);Promise#reduce
to
reduce
a list within a promise;Promise#spread
to map over a promise's value but,
instead of receiving a single value, spread the promise's value across
separate arguments:
Promise.of([1, 2]).spread(function (x, y) {
console.log(x);
console.log(y);
});
It also defines a monoid interface for Array
and String
, implementing
empty
such that:
Array.empty();
String.empty();
See the test
suite for more
information.
Usage
var Promise = require('pacta');
var p = new Promise();
setTimeout(function () {
p.resolve('Foo');
}, 1000);
p.map(console.log);
p.map(function (x) {
return x + '!';
}).map(console.log);
var p2 = new Promise();
setTimeout(function () {
p2.resolve(['bar']);
}, 500);
var p3 = Promise.of(['baz']);
p2.concat(p3).map(console.log);
p.conjoin(p2).map(console.log);
Promise.of([]).append(p).append(p2).map(console.log);
p2.append(p).spread(function (x, y) {
console.log(x);
console.log(y);
});
var p4 = new Promise();
setTimeout(function () {
p4.reject('Error!');
}, 1000);
p4.onRejected(function (reason) {
console.error('Failed due to', reason);
});
p4.then(function (value) {
console.log('Success!', value);
}, function (reason) {
console.error('Failure!', reason);
});
API Documentation
Promise()
var promise = new Promise();
Create a new, unfulfilled promise that will eventually be populated with a
value (through resolve
).
Promise.of(x)
var promise = Promise.of(1);
var promise = Promise.of('foo');
Create a new, fulfilled promise already populated with a value x
.
Promise#resolve(x)
var promise = new Promise();
promise.resolve(5);
Populate a promise with the value x
thereby resolving it.
This function can also be called as Promise#fulfill
.
Promise#reject(reason)
var promise = new Promise();
promise.reject('Errored out!');
Mark a promise as rejected, populating it with a reason.
Promise#map(f)
var promise = Promise.of(2);
promise.map(function (x) {
console.log(x);
return x * 2;
});
promise.map(function (x) {
return Promise.of(x * 2);
});
Execute a function f
on the contents of the promise. This returns a new
promise containing the result of applying f
to the initial promise's value.
In Haskell notation, its type signature is:
map :: Promise e a -> (a -> b) -> Promise e b
Note that this is the primary way of acting on the value of a promise: you can
use side-effects within your given function (e.g. console.log
) as well as
modifying the value and returning it in order to affect the returning
promise.
Note that any uncaught exceptions during the execution of f
will result in
the promise being rejected
with the exception as its reason
.
Promise#mapError(f)
var promise = new Promise();
promise.reject('Type error at line 214');
promise.mapError(function (x) {
console.log(x);
return 'Error: ' + x;
});
Execute a function f
on the reason of a rejected promise. This returns a new
rejected promise containing the result of applying f
to the initial
promise's reason.
In Haskell notation, its type signature is:
mapError :: Promise e a -> (e -> f) -> Promise f a
Note that any uncaught exceptions during the execution of f
will result in
the promise being rejected
with the exception as its reason
.
### Promise#then([onFulfilled[, onRejected]])
promise.then(function (value) {
return x * 2;
});
promise.then(function (value) {
return Promise.of(x * 2);
});
promise.then(function (value) {
console.log('Success!', value);
}, function (reason) {
console.error('Error!', reason);
});
An implementation of the Promises/A+ then
method, taking an
optional onFulfilled
and onRejected
function to call when the promise is
fulfilled or rejected respectively.
Like Promise#map
, then
returns a promise itself and can be
chained.
Promise#onRejected(f)
var p = new Promise();
p.reject('Error!');
p.onRejected(function (reason) {
console.error('Failed:', reason);
});
Identical to Promise#map
but only executed when a promise is
rejected rather than resolved.
Note that onRejected
returns a promise itself that is fulfilled by the given
function, f
. In this way, you can gracefully recover from errors like so:
var p = new Promise();
p.reject('Error!');
p.onRejected(function (reason) {
return 'Some safe default';
}).map(console.log);
Like Promise#map
, any uncaught exceptions within f
will
result in a rejected
promise:
var p = new Promise();
p.reject('Error!');
p.onRejected(function (reason) {
throw 'Another error!';
}).onRejected(console.log);
Promise#concat(p)
var promise = Promise.of('foo'),
promise2 = Promise.of('bar');
promise.concat(promise2);
Concatenate the promise with another promise p
into one containing both
values concatenated together. This will work for any promise containing a
semigroup (viz. a value that supports concat
) such as String
or Array
.
Note that concat
's usual
behaviour
of joining arrays, etc. applies.
Its type signature is:
concat :: Promise e a -> Promise e a -> Promise e a
If either of the original two promises is rejected, the resulting concatenated
promise will also be rejected. Note that only the first rejection will count
as further rejections will be ignored.
See also Promise#conjoin
and
Promise#append
.
Promise#chain(f)
var promise = Promise.of(2);
promise.chain(function (x) { return Promise.of(x * 2); });
Execute a function f
with the value of the promise. This differs from
Promise#map
in that the function must return a promise
itself.
Its type signature is:
chain :: Promise e a -> (a -> Promise e b) -> Promise e b
Promise#chainError(f)
var criticalAjaxCallThatMayFail = function() {
var p = new Promise();
setTimeout(function () {
if (Date.now() % 2 === 0) {
p.reject('Request timed out');
} else {
p.resolve('This is a critical sentence.');
}
}, 2000);
return p;
};
var getMessage = function (error) {
if (error) {
console.error('Error received: ' + error);
console.log('Retrying...');
}
console.log('Sending request...');
return criticalAjaxCallThatMayFail();
};
getMessage()
.chainError(getMessage)
.chainError(getMessage)
.map(console.log)
.mapError(console.error);
Execute a function f
with the reason of a rejected promise. This differs
from Promise#mapError
in that the function must
return a promise itself.
Its type signature is:
chainError :: Promise e a -> (e -> Promise f a) -> Promise f a
Promise#ap(p)
var promise = Promise.of(function (x) { return x * 2; }),
promise2 = Promise.of(2);
promise.ap(promise2);
On a promise containing a function, call that function with a promise p
containing a value.
Its type signature is:
ap :: Promise e (a -> b) -> Promise e a -> Promise e b
Promise#empty()
var promise = Promise.of('woo');
promise.empty();
On a promise containing a monoid (viz. something with an empty()
function on
itself or its constructor like Array
or String
), return a new promise with
an empty version of the initial value.
(Pacta ships with Monoid implementations for Array
and String
by default.)
Promise#conjoin(p)
var promise = Promise.of(1),
promise2 = Promise.of([2, 3]);
promise.conjoin(promise2);
Conjoin the promise with another promise p
, converting their values to
arrays if needed (e.g. 'foo'
into ['foo']
). This differs from
Promise#concat
which only works on promises of values
that are semigroups themselves.
All values are coerced to arrays using [].concat
.
Promise#append(p)
var promise = Promise.of([]),
promise2 = Promise.of([1]);
promise.append(promise2);
On a promise of a list, append another promise p
's value to it without
joining (e.g. appending [1]
to []
results in [[1]]
).
This is particularly useful when dealing with several promises containing
lists and you want to keep them separated instead of being merged into one as
would happen with Promise#concat
and
Promise#conjoin
.
Promise#reduce(f[, initialValue])
var promise = Promise.of([1, 2, 3]);
promise.reduce(function (acc, e) {
return acc + e;
}, 0);
On a promise containing an array,
reduce
its value, returning a promise of the resulting value. This defers to the
underlying signature of reduce
taking a function f
and an optional
initialValue
.
Promise#spread(f)
var promise = Promise.of([1, 2]);
promise.spread(function (x, y) {
return x + y;
});
Similar to Promise#map
, apply a function f
to a promise of
a list but, instead of receiving a single argument, pass each value of the
list to the function separately.
Contributors
Acknowledgements
James
Coglan
and Aanand Prasad convinced me
to explore the idea of monadic promises and Brian McKenna's "Fantasy Land"
specification and
feedback were essential.
License
Copyright © 2013 Paul Mucur.
Distributed under the MIT License.