Skip to content
/ pacta Public

An algebraic implementation of ECMAScript 2015 and Promises/A+ Promises in JavaScript for as many browsers and Node.js versions as possible

License

Notifications You must be signed in to change notification settings

mudge/pacta

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

90 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Pacta Build Status

An algebraic implementation of ECMAScript 2015 and Promises/A+ Promises in JavaScript for as many browsers and Node.js versions as possible.

Current version: 0.9.0
Supported Node.js versions: 0.6, 0.8, 0.10, 0.11, 0.12, 4.0, 4.1, 5.0
Supported browsers: Internet Explorer 6+, Firefox 3.6+, Chrome 14+, Opera 10.6+, Safari 4+, iOS 3+, Windows Phone 8.1, Android 2.2+

var promise = new Promise(function (resolve, reject) {
    setTimeout(function () { resolve('Hello'); }, 5000);
    setTimeout(function () { reject('Timeout!'); }, 60000);
});

promise
    .then(function (value) { return value + ', World!'; })
    .catch(function (reason) { return 'Sorry'; })
    .then(console.log); //=> Hello, World!

Installation

$ npm install pacta   # for Node.js
$ bower install pacta # for the browser

Alternatively, include pacta.js via a <script/> in your page (Pacta also supports using an AMD API-compliant loader such as RequireJS).

Promises

Promises can be thought of as objects representing a value that may not have been calculated yet (they are sometimes referred to as Deferreds).

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(function (resolve) {
    setTimeout(function () {
        /* Populate the promise with its final value. */
        resolve(1);
    }, 1000);
});

Promises can also be marked as rejected (viz. represent an error state) like so:

var p = new Promise(function (resolve, reject) {
    /* Mark the promise as rejected with a reason. */
    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 sample HTTP client and two example programs included in Pacta.

ECMAScript 2015

Pacta's promises comply with the Promise API described in ECMAScript 2015 and the Promises/A+ specification:

Algebraic JavaScript

The aforementioned high level functions are implemented in terms of the algebraic primitives defined in the "Fantasy Land" Algebraic JavaScript Specification:

These different specifications can be thought of as different levels of abstraction with ECMAScript 2015 at the top and Fantasy Land at the bottom, e.g.

Specification Functions
ECMAScript 2015 Promise.all, Promise.race, Promise.resolve, Promise.reject, Promise#catch
Promises/A+ Promise#then
Fantasy Land Promise#map, Promise#concat, Promise#chain, etc.

Pacta gives you access to all of these functions including the algebraic primitives for composition into more expressive operations.

Working with lists of promises

As well as the standard Promise.all and Promise.race, 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.all([1, 2]).spread(function (x, y) {
    console.log(x); //=> 1
    console.log(y); //=> 2
});

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.

API Documentation

new Promise([executor])

var promise = new Promise();
var promise = new Promise(function (resolve, reject) {
    if (foo) {
        resolve('Huzzah!');
    } else {
        reject('Oops!');
    }
});

Create a new, unfulfilled promise that will eventually be populated with a value either by an optionally passed executor function (which is passed a resolve and a reject function) or by Promise#resolve and Promise#reject.

See also

Promise#then([onFulfilled[, onRejected]])

promise.then(function (value) {
    return x * 2;
}); //=> Promise.resolve(4)

promise.then(function (value) {
    return Promise.resolve(x * 2);
}); //=> Promise.resolve(4)

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.

Unlike Promise#map, then will unwrap any promise that is returned by an onFulfilled or onRejected function (making then behave like Promise#chain).

See also

Promise#catch(onRejected)

promise.catch(function (reason) {
    console.error('Error!', reason);
});

An implementation of ECMAScript 2015's catch method, equivalent to calling Promise#then with an undefined onFulfilled.

Unlike Promise#onRejected, catch will unwrap any promises returned from the onRejected handler (making catch behave like Promise#chainRejected).

See also

Promise.resolve(value)

var promise = Promise.resolve(5);
var promise = Promise.resolve(promise);
var promise = Promise.resolve(thenable);

An implementation of ECMAScript 2015's Promise.resolve for returning a promise resolved with the given value.

Unlike Promise.of, if the given value is itself a promise or thenable (viz. a value with a then method) then Promise.resolve will unwrap it, resolving with its eventual state.

Note that this can be used to convert other promise implementations into Pacta promises (and is used internally by Pacta to do so).

See also

Promise.reject(reason)

var promise = Promise.reject('error!');
var promise = Promise.reject(new TypeError('Oops!'));

An implementation of ECMAScript 2015's Promise.reject for returning a promise rejected with a given reason.

See also

Promise.all(iterable)

var promise = new Promise(function (resolve, reject) {
    setTimeout(function () { resolve('foo'), 1000);
});

Promise.all(['bar', Promise.resolve(7), promise]).then(function (values) {
    console.log(values); //=> ['bar', 7, 'foo']
});

An implementation of ECMAScript 2015's Promise.all for returning a promise that resolves when all of the promises in a given iterable have resolved or rejects with the reason of the first passed promise that rejects.

Note that every element of the given iterable is passed to Promise.resolve to coerce it to a Pacta promise (this includes other promises, thenables and raw values).

If any promise in the iterable is rejected, the resulting promise will instantly reject with that promise's reason, e.g.

var promise = new Promise(function (resolve, reject) {
    setTimeout(function () { reject('oops!'); }, 500);
});

Promise.all(['bar', promise]).catch(function (reason) {
    console.error(reason); //=> 'oops!'
});

See also

Promise.race(iterable)

var p1 = new Promise(function (resolve) {
        setTimeout(function () { resolve('second'); }, 1000);
    }),
    p2 = new Promise(function (resolve) {
        setTimeout(function () { resolve('first!'); }, 500);
    });

Promise.race([p1, p2]).then(function (value) {
    console.log(value); //=> 'first!'
});

An implementation of ECMAScript 2015's Promise.race to return a promise that resolves or rejects as soon as one of the promises in the iterable resolves or rejects with the value or reason from that promise.

See also

Promise.of(x)

var promise = Promise.of(1);
var promise = Promise.of('foo');
var promise = Promise.of(Promise.of(1));

Create a new, fulfilled promise already populated with a value x.

Unlike Promise.resolve, Promise.of will not unwrap x if it is a promise or thenable itself so it is possible to created nested promises.

See also

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.of(4)

promise.map(function (x) {
    return Promise.of(x * 2);
}); //=> Promise.of(Promise.of(4))

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 a -> (a -> b) -> Promise 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.

See also

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);
//=> Logs "Some safe default"

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);
//=> Logs "Another error!"

Promise#chain(f)

var promise = Promise.of(2);

promise.chain(function (x) { return Promise.of(x * 2); }); //=> Promise.of(4)

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 a -> (a -> Promise b) -> Promise b

See also

Promise#chainRejected(f)

var promise = new Promise(function (resolve, reject) {
    reject('error!');
});

promise.chainRejected(function (reason) {
    return Promise.of('Phew!');
}); //=> Promise.of('Phew!')

Identical to Promise#chain but only executed when a promise is rejected rather than resolved.

This function can also be called as Promise#chainError.

Promise#ap(p)

var promise = Promise.of(function (x) { return x * 2; }),
    promise2 = Promise.of(2);

promise.ap(promise2); //=> Promise.of(4)

On a promise containing a function, call that function with a promise p containing a value.

Its type signature is:

ap :: Promise (a -> b) -> Promise a -> Promise b

See also

Promise#empty()

var promise = Promise.of('woo');

promise.empty(); //=> Promise.of('')

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.)

See also

Promise#concat(p)

var promise = Promise.of('foo'),
    promise2 = Promise.of('bar');

promise.concat(promise2); //=> Promise.of('foobar')

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 a -> Promise a -> Promise 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(p)

var promise = Promise.of(1),
    promise2 = Promise.of([2, 3]);

promise.conjoin(promise2); //=> Promise.of([1, 2, 3])

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); //=> Promise.of([[1]])

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); //=> Promise.of(6)

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;
}); //=> Promise.of(3)

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

  • Fixes to chain, chainError and empty were contributed by Ben Schulz;
  • mapError and chainError were contributed by Rodolphe Belouin.

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.

Fantasy Land Promises/A+

License

Copyright © 2013—2015 Paul Mucur.

Distributed under the MIT License.

About

An algebraic implementation of ECMAScript 2015 and Promises/A+ Promises in JavaScript for as many browsers and Node.js versions as possible

Resources

License

Stars

Watchers

Forks

Packages

No packages published