Comparing version 0.0.4 to 0.1.0
149
lib/pacta.js
@@ -6,2 +6,3 @@ var events = require('events'); | ||
this.resolved = false; | ||
this.rejected = false; | ||
@@ -14,14 +15,57 @@ if (arguments.length) { | ||
/* Populate a promise with its final value. */ | ||
Promise.prototype.resolve = function (x) { | ||
this.value = x; | ||
this.resolved = true; | ||
this.emitter.emit('resolved', x); | ||
Promise.prototype.resolve = Promise.prototype.fulfill = function (x) { | ||
if (this.state() === 'pending') { | ||
this.value = x; | ||
this.resolved = true; | ||
this.emitter.emit('resolved', x); | ||
} | ||
}; | ||
/* Reject a promise, populating it with a reason. */ | ||
Promise.prototype.reject = function (reason) { | ||
if (this.state() === 'pending') { | ||
this.reason = reason; | ||
this.rejected = true; | ||
this.emitter.emit('rejected', reason); | ||
} | ||
}; | ||
/* Return a promise's current state. */ | ||
Promise.prototype.state = function () { | ||
if (this.resolved) { | ||
return 'fulfilled'; | ||
} else if (this.rejected) { | ||
return 'rejected'; | ||
} else { | ||
return 'pending'; | ||
} | ||
}; | ||
/* onRejected :: Promise a -> (a -> b) -> Promise b */ | ||
Promise.prototype.onRejected = function (f) { | ||
var promise = new Promise(), | ||
reason = this.reason; | ||
if (this.rejected) { | ||
process.nextTick(function () { | ||
promise.resolve(f(reason)); | ||
}); | ||
} else { | ||
this.emitter.once('rejected', function (reason) { | ||
promise.resolve(f(reason)); | ||
}); | ||
} | ||
return promise; | ||
}; | ||
/* map :: Promise a -> (a -> b) -> Promise b */ | ||
Promise.prototype.map = function (f) { | ||
var promise = new Promise(); | ||
var promise = new Promise(), | ||
value = this.value; | ||
if (this.resolved) { | ||
promise.resolve(f(this.value)); | ||
process.nextTick(function () { | ||
promise.resolve(f(value)); | ||
}); | ||
} else { | ||
@@ -38,21 +82,17 @@ this.emitter.once('resolved', function (x) { | ||
Promise.prototype.chain = function (f) { | ||
if (this.resolved) { | ||
return f(this.value); | ||
} else { | ||
var promise = new Promise(); | ||
var promise = new Promise(); | ||
/* Map over the given Promise a with (a -> Promise b), returning | ||
* a new Promise (Promise b). Map over that, thereby gaining access to | ||
* the inner Promise b. Map over that in order to get to the inner value | ||
* of b and resolve another promise with it. Return that promise as | ||
* it is equivalent to Promise b. | ||
*/ | ||
this.map(f).map(function (x) { | ||
x.map(function (y) { | ||
promise.resolve(y); | ||
}); | ||
/* Map over the given Promise a with (a -> Promise b), returning a new | ||
* Promise (Promise b). Map over that, thereby gaining access to the inner | ||
* Promise b. Map over that in order to get to the inner value of b and | ||
* resolve another promise with it. Return that promise as it is equivalent | ||
* to Promise b. | ||
*/ | ||
this.map(f).map(function (x) { | ||
x.map(function (y) { | ||
promise.resolve(y); | ||
}); | ||
}); | ||
return promise; | ||
} | ||
return promise; | ||
}; | ||
@@ -85,5 +125,5 @@ | ||
/* conjoin :: Promise a -> Promise b -> Promise [a b] | ||
* conjoin :: Promise [a] -> Promise b -> Promise [a b] | ||
* conjoin :: Promise a -> Promise [b] -> Promise [a b] | ||
/* conjoin :: Promise a -> Promise b -> Promise [a b] | ||
* conjoin :: Promise a -> Promise [b] -> Promise [a b] | ||
* conjoin :: Promise [a] -> Promise b -> Promise [a b] | ||
* conjoin :: Promise [a] -> Promise [b] -> Promise [a b] | ||
@@ -119,2 +159,61 @@ */ | ||
/* Compatibility with the Promises/A+ specification. */ | ||
Promise.prototype.then = function (onFulfilled, onRejected) { | ||
var promise = new Promise(), | ||
isPromise = function (x) { | ||
return x !== null && typeof x === 'object' && | ||
typeof x.then === 'function'; | ||
}; | ||
if (typeof onFulfilled === 'function') { | ||
this.map(function (x) { | ||
try { | ||
var value = onFulfilled(x); | ||
if (isPromise(value)) { | ||
value.then(function (x) { | ||
promise.resolve(x); | ||
}, function (reason) { | ||
promise.reject(reason); | ||
}); | ||
} else { | ||
promise.resolve(value); | ||
} | ||
} catch (e) { | ||
promise.reject(e); | ||
} | ||
}); | ||
} else { | ||
this.map(function (x) { | ||
promise.resolve(x); | ||
}); | ||
} | ||
if (typeof onRejected === 'function') { | ||
this.onRejected(function (reason) { | ||
try { | ||
var reason = onRejected(reason); | ||
if (isPromise(reason)) { | ||
reason.then(function (x) { | ||
promise.resolve(x); | ||
}, function (reason) { | ||
promise.reject(reason); | ||
}); | ||
} else { | ||
promise.resolve(reason); | ||
} | ||
} catch (e) { | ||
promise.reject(e); | ||
} | ||
}); | ||
} else { | ||
this.onRejected(function (reason) { | ||
promise.reject(reason); | ||
}); | ||
} | ||
return promise; | ||
}; | ||
/* of :: a -> Promise a */ | ||
@@ -121,0 +220,0 @@ Promise.of = function (x) { |
{ | ||
"name": "pacta", | ||
"description": "An algebraic implementation of Promises.", | ||
"description": "An algebraic, Promises/A+ compliant implementation of Promises.", | ||
"homepage": "https://github.com/mudge/pacta", | ||
"author": "Paul Mucur (http://mudge.name)", | ||
"keywords": ["promises", "monad", "functor"], | ||
"version": "0.0.4", | ||
"keywords": ["promises", "monad", "functor", "promises-aplus"], | ||
"version": "0.1.0", | ||
"main": "./lib/pacta.js", | ||
"dependencies": {}, | ||
"devDependencies": { "mocha": "1.10.0" }, | ||
"devDependencies": { | ||
"mocha": "1.10.0", | ||
"promises-aplus-tests": "1.3.1" | ||
}, | ||
"scripts": { "test": "mocha" }, | ||
@@ -12,0 +15,0 @@ "repository": { |
141
README.md
# pacta [![Build Status](https://travis-ci.org/mudge/pacta.png?branch=master)](https://travis-ci.org/mudge/pacta) | ||
This is an implementation of algebraic Promises in | ||
[node.js](http://nodejs.org). | ||
```javascript | ||
{ 'pacta': '0.1.0' } | ||
``` | ||
This is an implementation of [algebraic][Fantasy Land], [Promises/A+][A+] | ||
compliant Promises in [node.js](http://nodejs.org). | ||
Promises can be thought of as objects representing a value that may not have | ||
been calculated yet (similar to the [Maybe monad][Maybe]). 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. | ||
been calculated yet (they are sometimes referred to as `Deferred`s). | ||
For a worked example of this, see the | ||
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: | ||
```javascript | ||
var Promise = require('pacta').Promise; | ||
var p = new Promise(); | ||
setTimeout(function () { | ||
/* Populate the promise with its final value. */ | ||
p.resolve(1); | ||
}, 1000); | ||
``` | ||
Promises can also be marked as `rejected` (viz. represent an error state) like | ||
so: | ||
```javascript | ||
/* Mark the promise as rejected with a reason. */ | ||
p.reject('The server could not be found.'); | ||
``` | ||
For a worked example of using promises, see the | ||
[two](https://github.com/mudge/pacta/blob/master/example/codenames.js) | ||
[example programs](https://github.com/mudge/pacta/blob/master/example/codenames-2.js) | ||
[example | ||
programs](https://github.com/mudge/pacta/blob/master/example/codenames-2.js) | ||
and [sample HTTP | ||
@@ -23,3 +50,3 @@ client](https://github.com/mudge/pacta/blob/master/example/promised-http.js) | ||
in the [Fantasy Land | ||
Specification](https://github.com/puffnfresh/fantasy-land): | ||
Specification][Fantasy Land]: | ||
@@ -40,5 +67,17 @@ * [Semigroups](https://github.com/puffnfresh/fantasy-land#semigroup) (through | ||
As well as above, Pacta also provides the following functions for creating and | ||
working with Promises of lists: | ||
Pacta's promises are compliant with the [Promises/A+ | ||
specification](http://promises-aplus.github.io/promises-spec), providing | ||
a [`then` method](#promisethenonfulfilled-onrejected). | ||
Promises are resolved (or fulfilled) with | ||
[`Promise#resolve`](#promiseresolvex) and rejected with | ||
[`Promise#rejectreason`](#promiserejectreason). | ||
To execute code on rejection without using | ||
[`Promise#then`](#promisethenonfulfilled-onrejected), use | ||
[`Promise#onRejected`](#promiseonrejectedf). | ||
Pacta also provides the following functions for creating and working with | ||
Promises of lists: | ||
* [`Promise#conjoin`](#promiseconjoinp) to concatenate promises into a list of | ||
@@ -75,5 +114,2 @@ values regardless of their original type meaning that non-Monoid types can | ||
Note that Pacta does not handle errors or the concept of a failed promise as | ||
yet. | ||
See [the test | ||
@@ -83,2 +119,4 @@ suite](https://github.com/mudge/pacta/blob/master/test/pacta_test.js) for more | ||
[A+]: http://promises-aplus.github.io/promises-spec/ | ||
[Fantasy Land]: https://github.com/puffnfresh/fantasy-land | ||
[Maybe]: https://en.wikipedia.org/wiki/Monad_(functional_programming)#The_Maybe_monad | ||
@@ -116,6 +154,21 @@ | ||
p2.append(p).explode(function (x, y) { | ||
p2.append(p).spread(function (x, y) { | ||
console.log(x); //=> 'bar' | ||
console.log(y); //=> 'Foo' | ||
}); | ||
var p4 = new Promise(); | ||
setTimeout(function () { | ||
p4.reject('Error!'); | ||
}, 1000); | ||
p4.onRejected(function (reason) { | ||
console.error('Failed due to', reason); | ||
}); //=> Failed due to Error! | ||
p4.then(function (value) { | ||
console.log('Success!', value); | ||
}, function (reason) { | ||
console.error('Failure!', reason); | ||
}); //=> Failure! Error! | ||
``` | ||
@@ -152,2 +205,13 @@ | ||
*This function can also be called as `Promise#fulfill`.* | ||
### `Promise#reject(reason)` | ||
```javascript | ||
var promise = new Promise(); | ||
promise.reject('Errored out!'); | ||
``` | ||
Mark a promise as rejected, populating it with a reason. | ||
### `Promise#map(f)` | ||
@@ -163,2 +227,6 @@ | ||
}); //=> Promise.of(4) | ||
promise.map(function (x) { | ||
return Promise.of(x * 2); | ||
}); //=> Promise.of(Promise.of(4)) | ||
``` | ||
@@ -180,2 +248,41 @@ | ||
### `Promise#then([onFulfilled[, onRejected]])` | ||
```javascript | ||
promise.then(function (value) { | ||
return x * 2; | ||
}); //=> Promise.of(4) | ||
promise.then(function (value) { | ||
return Promise.of(x * 2); | ||
}); //=> Promise.of(4) | ||
promise.then(function (value) { | ||
console.log('Success!', value); | ||
}, function (reason) { | ||
console.error('Error!', reason); | ||
}); | ||
``` | ||
An implementation of the [Promises/A+ `then` | ||
method](http://promises-aplus.github.io/promises-spec/#the__method), taking an | ||
optional `onFulfilled` and `onRejected` function to call when the promise is | ||
fulfilled or rejected respectively. | ||
Like [`Promise#map`](#promisemapf), `then` returns a promise itself and can be | ||
chained. | ||
### `Promise#onRejected(f)` | ||
```javascript | ||
var p = new Promise(); | ||
p.reject('Error!'); | ||
p.onRejected(function (reason) { | ||
console.error('Failed:', reason); | ||
}); | ||
``` | ||
Identical to [`Promise#map`](#promisemapf) but only executed when a promise is | ||
rejected rather than resolved. | ||
### `Promise#concat(p)` | ||
@@ -182,0 +289,0 @@ |
var assert = require('assert'), | ||
Promise = require('../lib/pacta').Promise; | ||
Promise = require('../lib/pacta').Promise, | ||
adapter = require('./pacta_adapter'); | ||
@@ -27,5 +28,6 @@ describe('Promise', function () { | ||
describe('.of', function () { | ||
it('wraps a value in a new promise', function () { | ||
it('wraps a value in a new promise', function (done) { | ||
Promise.of(1).map(function (x) { | ||
assert.equal(1, x); | ||
done(); | ||
}); | ||
@@ -35,2 +37,118 @@ }); | ||
describe('#state', function () { | ||
it('is pending for unfulfilled and unrejected promises', function () { | ||
var p = new Promise(); | ||
assert.equal('pending', p.state()); | ||
}); | ||
it('is fulfilled for fulfilled promises', function () { | ||
var p = Promise.of(1); | ||
assert.equal('fulfilled', p.state()); | ||
}); | ||
it('is rejected for rejected promises', function () { | ||
var p = new Promise(); | ||
p.reject('error'); | ||
assert.equal('rejected', p.state()); | ||
}); | ||
}); | ||
describe('#resolve', function () { | ||
it('resolves a promise with its final value', function () { | ||
var p = new Promise(); | ||
p.resolve(1); | ||
assert.equal('fulfilled', p.state()); | ||
}); | ||
it('triggers any listeners for resolution', function (done) { | ||
var triggered = false, | ||
p = new Promise(); | ||
p.map(function () { | ||
triggered = true; | ||
done(); | ||
}); | ||
p.resolve(1); | ||
assert.ok(triggered); | ||
}); | ||
it('does nothing to rejected promises', function () { | ||
var p = new Promise(); | ||
p.reject('error'); | ||
p.resolve(1); | ||
assert.equal('rejected', p.state()); | ||
}); | ||
it('does not trigger listeners if the promise is rejected', function () { | ||
var triggered = false, | ||
p = new Promise(); | ||
p.reject('error'); | ||
p.map(function () { | ||
triggered = true; | ||
}); | ||
p.resolve(1); | ||
assert.ok(!triggered); | ||
}); | ||
}); | ||
describe('#reject', function () { | ||
it('rejects a promise, setting a reason', function () { | ||
var p = new Promise(); | ||
p.reject('error'); | ||
assert.equal('rejected', p.state()); | ||
}); | ||
it('does nothing to fulfilled promises', function () { | ||
var p = Promise.of(1); | ||
p.reject('error'); | ||
assert.equal('fulfilled', p.state()); | ||
}); | ||
it('triggers onRejected listeners', function (done) { | ||
var triggered = false, | ||
p = new Promise(); | ||
p.onRejected(function (reason) { | ||
triggered = true; | ||
done(); | ||
}); | ||
p.reject('error'); | ||
assert.ok(triggered); | ||
}); | ||
it('does not trigger onRejected listeners if already fulfilled', function () { | ||
var triggered = false, | ||
p = Promise.of(1); | ||
p.onRejected(function (reason) { | ||
triggered = true; | ||
}); | ||
p.reject('error'); | ||
assert.ok(!triggered); | ||
}); | ||
}); | ||
describe('#onRejected', function () { | ||
it('binds a listener to be fired on rejection', function (done) { | ||
var p = new Promise(); | ||
p.reject('error'); | ||
p.onRejected(function (reason) { | ||
assert.equal('error', reason); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe('#map', function () { | ||
@@ -106,2 +224,66 @@ it('yields the value of the promise', function (done) { | ||
describe('#then', function () { | ||
it('yields its value like #map', function (done) { | ||
p.then(function (x) { | ||
assert.equal('foo', x); | ||
done(); | ||
}); | ||
}); | ||
it('can be chained when returning a value', function (done) { | ||
p.then(function (x) { | ||
return x + '!'; | ||
}).then(function (x) { | ||
assert.equal('foo!', x); | ||
done(); | ||
}); | ||
}); | ||
it('does not wrap a promise in a promise', function (done) { | ||
p.then(function (x) { | ||
return Promise.of(x); | ||
}).map(function (x) { | ||
assert.equal('foo', x); | ||
done(); | ||
}); | ||
}); | ||
it('always returns a promise', function () { | ||
assert.equal('object', typeof p.then()); | ||
}); | ||
it('returns a fulfilled promise with the return value of onRejected', function (done) { | ||
var p = new Promise(); | ||
p.reject('foo'); | ||
var p2 = p.then(function (value) { | ||
return 1; | ||
}, function (reason) { | ||
return 'error'; | ||
}); | ||
p2.map(function (x) { | ||
assert.equal('error', x); | ||
assert.equal('fulfilled', p2.state()); | ||
done(); | ||
}); | ||
}); | ||
it('assumes the return value of onFulfilled', function (done) { | ||
var p = Promise.of('foo'), | ||
p2 = p.then(function (value) { | ||
return 1; | ||
}, function (reason) { | ||
return 'error'; | ||
}); | ||
p2.map(function (x) { | ||
assert.equal(1, x); | ||
assert.equal('fulfilled', p2.state()); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
describe('#concat', function () { | ||
@@ -384,2 +566,6 @@ it('fulfils the associativity property of semigroups #1', function (done) { | ||
}); | ||
describe('Promises/A+ compliance', function () { | ||
require('promises-aplus-tests').mocha(adapter); | ||
}); | ||
}); | ||
@@ -386,0 +572,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
41864
11
779
433
2