diff --git a/.gitignore b/.gitignore
index 3c3629e..a088b6f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
node_modules
+bower_components
diff --git a/README.md b/README.md
index e103fbf..63edff7 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,17 @@
# pacta [![Build Status](https://travis-ci.org/mudge/pacta.png?branch=master)](https://travis-ci.org/mudge/pacta)
```javascript
-{ 'pacta': '0.2.0' }
+{ 'pacta': '0.3.0' }
+```
+
+```shell
+$ npm install pacta # for node
+$ bower install pacta # for the browser
```
This is an implementation of [algebraic][Fantasy Land], [Promises/A+][A+]
-compliant Promises in [node.js](http://nodejs.org).
+compliant Promises in JavaScript (both for the browser and
+[node.js](http://nodejs.org)).
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).
@@ -19,7 +25,8 @@ 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;
+/* Include pacta.js or require explicitly in node.js: */
+var Promise = require('pacta');
var p = new Promise();
setTimeout(function () {
@@ -124,7 +131,7 @@ information.
## Usage
```javascript
-var Promise = require('pacta').Promise;
+var Promise = require('pacta');
var p = new Promise();
setTimeout(function () {
diff --git a/bower.json b/bower.json
new file mode 100644
index 0000000..113cb21
--- /dev/null
+++ b/bower.json
@@ -0,0 +1,19 @@
+{
+ "name": "pacta",
+ "description": "An algebraic, Promises/A+ compliant implementation of Promises.",
+ "keywords": ["promises", "monad", "functor", "promises-aplus"],
+ "authors": ["Paul Mucur"],
+ "version": "0.3.0",
+ "main": "lib/pacta.js",
+ "devDependencies": {
+ "mocha": "1.12.0",
+ "proclaim": "1.5.0"
+ },
+ "ignore": [
+ "bower_components",
+ "node_modules",
+ "example",
+ "images",
+ "test"
+ ]
+}
diff --git a/lib/pacta.js b/lib/pacta.js
index aabb0c5..cf711eb 100644
--- a/lib/pacta.js
+++ b/lib/pacta.js
@@ -1,245 +1,367 @@
-'use strict';
+/*global module, define, process, setImmediate, setTimeout */
+(function (name, context, definition) {
+ 'use strict';
+
+ if (typeof module === 'object' && module.exports) {
+ module.exports = definition();
+ } else if (typeof define === 'function' && define.amd) {
+ define(definition);
+ } else {
+ context[name] = definition();
+ }
+}('Promise', this, function () {
+ 'use strict';
-var events = require('events');
+ var nextTick, indexOf, EventEmitter, Promise, thenable, reduce;
-var Promise = function (value) {
- this.emitter = new events.EventEmitter();
- this.resolved = false;
- this.rejected = false;
+ if (typeof Array.prototype.reduce !== 'function') {
+ reduce = function (array, callback, initialValue) {
+ var i, value, length = array.length, isValueSet = false;
- if (arguments.length) {
- this.resolve(value);
- }
-};
-
-/* Populate a promise with its final value. */
-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);
- }
-};
+ if (arguments.length > 2) {
+ value = initialValue;
+ isValueSet = true;
+ }
-/* Return a promise's current state. */
-Promise.prototype.state = function () {
- var result;
+ for (i = 0; i < length; i += 1) {
+ if (array.hasOwnProperty(i)) {
+ if (isValueSet) {
+ value = callback(value, array[i], i, array);
+ } else {
+ value = array[i];
+ isValueSet = true;
+ }
+ }
+ }
- if (this.resolved) {
- result = 'fulfilled';
- } else if (this.rejected) {
- result = 'rejected';
+ return value;
+ };
} else {
- result = 'pending';
+ reduce = function (array) {
+ return Array.prototype.reduce.apply(array, [].slice.call(arguments, 1));
+ };
}
- return result;
-};
-
-/* 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));
- });
+ /* Polyfill process.nextTick. */
+ if (typeof process === 'object' && process &&
+ typeof process.nextTick === 'function') {
+ nextTick = process.nextTick;
+ } else if (typeof setImmediate === 'function') {
+ nextTick = setImmediate;
} else {
- this.emitter.once('rejected', function (reason) {
- promise.resolve(f(reason));
- });
+ nextTick = function (f) {
+ setTimeout(f, 0);
+ };
}
- return promise;
-};
+ /* Polyfill Array#indexOf. */
+ if (typeof Array.prototype.indexOf === 'function') {
+ indexOf = function (haystack, needle) {
+ return haystack.indexOf(needle);
+ };
+ } else {
+ indexOf = function (haystack, needle) {
+ var i = 0, length = haystack.length, idx = -1, found = false;
-/* map :: Promise a -> (a -> b) -> Promise b */
-Promise.prototype.map = function (f) {
- var promise = new Promise(),
- value = this.value;
+ while (i < length && !found) {
+ if (haystack[i] === needle) {
+ idx = i;
+ found = true;
+ }
- if (this.resolved) {
- process.nextTick(function () {
- promise.resolve(f(value));
- });
- } else {
- this.emitter.once('resolved', function (x) {
- promise.resolve(f(x));
- });
+ i += 1;
+ }
+
+ return idx;
+ };
}
- return promise;
-};
-
-/* chain :: Promise a -> (a -> Promise b) -> Promise b */
-Promise.prototype.chain = function (f) {
- 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);
- });
- });
- return promise;
-};
+ /* Polyfill EventEmitter. */
+ EventEmitter = function () {
+ this.events = {};
+ };
-/* concat :: Promise a -> Promise a */
-Promise.prototype.concat = function (other) {
- var promise = new Promise();
+ EventEmitter.prototype.on = function (event, listener) {
+ if (typeof this.events[event] !== 'object') {
+ this.events[event] = [];
+ }
- this.map(function (x) {
- other.map(function (y) {
- promise.resolve(x.concat(y));
- });
- });
-
- this.onRejected(function (reason) {
- promise.reject(reason);
- });
-
- other.onRejected(function (reason) {
- promise.reject(reason);
- });
-
- return promise;
-};
-
-/* ap :: Promise (a -> b) -> Promise a -> Promise b */
-Promise.prototype.ap = function (m) {
- return this.chain(function (f) {
- return m.map(f);
- });
-};
-
-/* empty :: Promise a -> Promise a */
-Promise.prototype.empty = function () {
- return Promise.of(this.value.empty ? this.value.empty() : this.value.constructor.empty());
-};
-
-/* 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]
- */
-Promise.prototype.conjoin = function (other) {
- var wrap = function (x) { return [].concat(x); };
-
- return this.map(wrap).concat(other.map(wrap));
-};
-
-/* append :: Promise [a] -> Promise b -> Promise [a b] */
-Promise.prototype.append = function (other) {
- return this.concat(other.map(function (x) {
- return [x];
- }));
-};
-
-/* spread :: Promise a -> (a -> b) -> Promise b */
-Promise.prototype.spread = Promise.prototype.explode = function (f) {
- return this.map(function (x) {
- return f.apply(null, x);
- });
-};
-
-Promise.prototype.reduce = function () {
- var args = [].slice.call(arguments);
-
- return this.map(function (x) {
- return x.reduce.apply(x, args);
- });
-};
-
-/* Determine whether a value is "thenable" in Promises/A+ terminology. */
-var thenable = function (x) {
- return x !== null && typeof x === 'object' &&
- typeof x.then === 'function';
-};
-
-/* Compatibility with the Promises/A+ specification. */
-Promise.prototype.then = function (onFulfilled, onRejected) {
- var promise = new Promise();
-
- if (typeof onFulfilled === 'function') {
- this.map(function (x) {
- try {
- var value = onFulfilled(x);
-
- if (thenable(value)) {
- value.then(function (x) {
- promise.resolve(x);
- }, function (reason) {
- promise.reject(reason);
- });
- } else {
- promise.resolve(value);
- }
- } catch (e) {
- promise.reject(e);
+ this.events[event].push(listener);
+ };
+
+ EventEmitter.prototype.removeListener = function (event, listener) {
+ var idx;
+
+ if (typeof this.events[event] === 'object') {
+ idx = indexOf(this.events[event], listener);
+
+ if (idx > -1) {
+ this.events[event].splice(idx, 1);
}
+ }
+ };
+
+ EventEmitter.prototype.emit = function (event) {
+ var i, listeners, length, args = [].slice.call(arguments, 1);
+
+ if (typeof this.events[event] === 'object') {
+ listeners = this.events[event].slice();
+ length = listeners.length;
+
+ for (i = 0; i < length; i += 1) {
+ listeners[i].apply(this, args);
+ }
+ }
+ };
+
+ EventEmitter.prototype.once = function (event, listener) {
+ this.on(event, function g() {
+ this.removeListener(event, g);
+ listener.apply(this, arguments);
});
- } else {
- this.map(function (x) {
- promise.resolve(x);
+ };
+
+ /* A Promise. */
+ Promise = function (value) {
+ this.emitter = new EventEmitter();
+ this.resolved = false;
+ this.rejected = false;
+
+ if (arguments.length) {
+ this.resolve(value);
+ }
+ };
+
+ /* Populate a promise with its final value. */
+ 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 () {
+ var result;
+
+ if (this.resolved) {
+ result = 'fulfilled';
+ } else if (this.rejected) {
+ result = 'rejected';
+ } else {
+ result = 'pending';
+ }
+
+ return result;
+ };
+
+ /* onRejected :: Promise a -> (a -> b) -> Promise b */
+ Promise.prototype.onRejected = function (f) {
+ var promise = new Promise(),
+ reason = this.reason;
+
+ if (this.rejected) {
+ 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(),
+ value = this.value;
+
+ if (this.resolved) {
+ nextTick(function () {
+ promise.resolve(f(value));
+ });
+ } else {
+ this.emitter.once('resolved', function (x) {
+ promise.resolve(f(x));
+ });
+ }
+
+ return promise;
+ };
+
+ /* chain :: Promise a -> (a -> Promise b) -> Promise b */
+ Promise.prototype.chain = function (f) {
+ 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);
+ });
});
- }
- if (typeof onRejected === 'function') {
- this.onRejected(function (reason) {
- try {
- reason = onRejected(reason);
-
- if (thenable(reason)) {
- reason.then(function (x) {
- promise.resolve(x);
- }, function (reason) {
- promise.reject(reason);
- });
- } else {
- promise.resolve(reason);
- }
- } catch (e) {
- promise.reject(e);
- }
+ return promise;
+ };
+
+ /* concat :: Promise a -> Promise a */
+ Promise.prototype.concat = function (other) {
+ var promise = new Promise();
+
+ this.map(function (x) {
+ other.map(function (y) {
+ promise.resolve(x.concat(y));
+ });
});
- } else {
+
this.onRejected(function (reason) {
promise.reject(reason);
});
- }
- return promise;
-};
+ other.onRejected(function (reason) {
+ promise.reject(reason);
+ });
-/* of :: a -> Promise a */
-Promise.of = function (x) {
- return new Promise(x);
-};
+ return promise;
+ };
-/* A Monoid interface for Array. */
-Array.empty = function () {
- return [];
-};
+ /* ap :: Promise (a -> b) -> Promise a -> Promise b */
+ Promise.prototype.ap = function (m) {
+ return this.chain(function (f) {
+ return m.map(f);
+ });
+ };
+
+ /* empty :: Promise a -> Promise a */
+ Promise.prototype.empty = function () {
+ return Promise.of(this.value.empty ? this.value.empty() : this.value.constructor.empty());
+ };
+
+ /* 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]
+ */
+ Promise.prototype.conjoin = function (other) {
+ var wrap = function (x) { return [].concat(x); };
+
+ return this.map(wrap).concat(other.map(wrap));
+ };
+
+ /* append :: Promise [a] -> Promise b -> Promise [a b] */
+ Promise.prototype.append = function (other) {
+ return this.concat(other.map(function (x) {
+ return [x];
+ }));
+ };
+
+ /* spread :: Promise a -> (a -> b) -> Promise b */
+ Promise.prototype.spread = Promise.prototype.explode = function (f) {
+ return this.map(function (x) {
+ return f.apply(null, x);
+ });
+ };
-/* A Monoid interface for String. */
-String.empty = function () {
- return '';
-};
+ Promise.prototype.reduce = function () {
+ var args = [].slice.call(arguments);
-exports.Promise = Promise;
+ return this.map(function (x) {
+ return reduce.apply(null, [x].concat(args));
+ });
+ };
+
+ /* Determine whether a value is "thenable" in Promises/A+ terminology. */
+ thenable = function (x) {
+ return x !== null && typeof x === 'object' &&
+ typeof x.then === 'function';
+ };
+
+ /* Compatibility with the Promises/A+ specification. */
+ Promise.prototype.then = function (onFulfilled, onRejected) {
+ var promise = new Promise();
+
+ if (typeof onFulfilled === 'function') {
+ this.map(function (x) {
+ try {
+ var value = onFulfilled(x);
+
+ if (thenable(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 {
+ reason = onRejected(reason);
+
+ if (thenable(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 */
+ Promise.of = function (x) {
+ return new Promise(x);
+ };
+
+ /* A Monoid interface for Array. */
+ Array.empty = function () {
+ return [];
+ };
+
+ /* A Monoid interface for String. */
+ String.empty = function () {
+ return '';
+ };
+
+ return Promise;
+}));
diff --git a/package.json b/package.json
index a9a1bb0..61a4b42 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"homepage": "https://github.com/mudge/pacta",
"author": "Paul Mucur (http://mudge.name)",
"keywords": ["promises", "monad", "functor", "promises-aplus"],
- "version": "0.2.0",
+ "version": "0.3.0",
"main": "./lib/pacta.js",
"dependencies": {},
"devDependencies": {
diff --git a/test/browser_test.html b/test/browser_test.html
new file mode 100644
index 0000000..874ec95
--- /dev/null
+++ b/test/browser_test.html
@@ -0,0 +1,21 @@
+
+
+
+ Pacta Browser Test
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/pacta_adapter.js b/test/pacta_adapter.js
index 2a2b60c..b61d9fa 100644
--- a/test/pacta_adapter.js
+++ b/test/pacta_adapter.js
@@ -1,7 +1,7 @@
'use strict';
/* An adapter for the Promises/A+ compatibility suite. */
-var Promise = require('../lib/pacta').Promise;
+var Promise = require('../lib/pacta');
exports.fulfilled = function (value) {
return Promise.of(value);
diff --git a/test/pacta_test.js b/test/pacta_test.js
index 24b2f30..965075d 100644
--- a/test/pacta_test.js
+++ b/test/pacta_test.js
@@ -1,605 +1,608 @@
-/*global describe, it, beforeEach */
-'use strict';
-
-var assert = require('assert'),
- Promise = require('../lib/pacta').Promise,
- adapter = require('./pacta_adapter');
-
-describe('Promise', function () {
- var p, p2, p3;
-
- beforeEach(function () {
- p = new Promise();
- setTimeout(function () {
- p.resolve('foo');
- }, 50);
-
- p2 = new Promise();
- setTimeout(function () {
- p2.resolve('bar');
- }, 25);
-
- p3 = new Promise();
- setTimeout(function () {
- p3.resolve('baz');
- }, 75);
- });
-
- describe('.of', function () {
- it('wraps a value in a new promise', function (done) {
- Promise.of(1).map(function (x) {
- assert.equal(1, x);
- done();
+/*global describe, it, beforeEach, require, setTimeout */
+(function (context, test) {
+ 'use strict';
+
+ if (typeof context.assert === 'object' && typeof context.Promise === 'function') {
+ test(context.assert, context.Promise);
+ } else {
+ test(require('assert'), require('../lib/pacta'));
+ }
+}(this, function (assert, Promise) {
+ 'use strict';
+
+ describe('Promise', function () {
+ var p, p2, p3, p4;
+
+ beforeEach(function () {
+ p = new Promise();
+ setTimeout(function () {
+ p.resolve('foo');
+ }, 50);
+
+ p2 = new Promise();
+ setTimeout(function () {
+ p2.resolve('bar');
+ }, 25);
+
+ p3 = new Promise();
+ setTimeout(function () {
+ p3.resolve('baz');
+ }, 75);
+ });
+
+ describe('.of', function () {
+ it('wraps a value in a new promise', function (done) {
+ Promise.of(1).map(function (x) {
+ assert.equal(1, x);
+ done();
+ });
});
});
- });
-
- 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);
+ describe('#state', function () {
+ it('is pending for unfulfilled and unrejected promises', function () {
+ p = new Promise();
- assert.equal('fulfilled', p.state());
- });
+ assert.equal('pending', p.state());
+ });
- it('is rejected for rejected promises', function () {
- var p = new Promise();
- p.reject('error');
+ it('is fulfilled for fulfilled promises', function () {
+ p = Promise.of(1);
- assert.equal('rejected', p.state());
- });
- });
+ assert.equal('fulfilled', p.state());
+ });
- describe('#resolve', function () {
- it('resolves a promise with its final value', function () {
- var p = new Promise();
- p.resolve(1);
+ it('is rejected for rejected promises', function () {
+ p = new Promise();
+ p.reject('error');
- assert.equal('fulfilled', p.state());
+ assert.equal('rejected', p.state());
+ });
});
- it('triggers any listeners for resolution', function (done) {
- var triggered = false,
+ describe('#resolve', function () {
+ it('resolves a promise with its final value', function () {
p = new Promise();
+ p.resolve(1);
- p.map(function () {
- triggered = true;
- done();
+ assert.equal('fulfilled', p.state());
});
- p.resolve(1);
+ it('triggers any listeners for resolution', function (done) {
+ var triggered = false;
- assert.ok(triggered);
- });
+ p = new Promise();
+ p.map(function () {
+ triggered = true;
+ done();
+ });
- it('does nothing to rejected promises', function () {
- var p = new Promise();
- p.reject('error');
- p.resolve(1);
+ p.resolve(1);
- assert.equal('rejected', p.state());
- });
+ assert.ok(triggered);
+ });
- it('does not trigger listeners if the promise is rejected', function () {
- var triggered = false,
+ it('does nothing to rejected promises', function () {
p = new Promise();
+ p.reject('error');
+ p.resolve(1);
- p.reject('error');
- p.map(function () {
- triggered = true;
+ assert.equal('rejected', p.state());
});
- p.resolve(1);
- assert.ok(!triggered);
- });
- });
+ it('does not trigger listeners if the promise is rejected', function () {
+ var triggered = false;
- describe('#reject', function () {
- it('rejects a promise, setting a reason', function () {
- var p = new Promise();
- p.reject('error');
+ p = new Promise();
+ p.reject('error');
+ p.map(function () {
+ triggered = true;
+ });
+ p.resolve(1);
- assert.equal('rejected', p.state());
+ assert.ok(!triggered);
+ });
});
- it('does nothing to fulfilled promises', function () {
- var p = Promise.of(1);
- p.reject('error');
+ describe('#reject', function () {
+ it('rejects a promise, setting a reason', function () {
+ p = new Promise();
+ p.reject('error');
- assert.equal('fulfilled', p.state());
- });
+ assert.equal('rejected', p.state());
+ });
- it('triggers onRejected listeners', function (done) {
- var triggered = false,
- p = new Promise();
- p.onRejected(function () {
- triggered = true;
- done();
+ it('does nothing to fulfilled promises', function () {
+ p = Promise.of(1);
+ p.reject('error');
+
+ assert.equal('fulfilled', p.state());
});
- p.reject('error');
- assert.ok(triggered);
- });
+ it('triggers onRejected listeners', function (done) {
+ var triggered = false;
- it('does not trigger onRejected listeners if already fulfilled', function () {
- var triggered = false,
- p = Promise.of(1);
- p.onRejected(function () {
- triggered = true;
+ p = new Promise();
+ p.onRejected(function () {
+ triggered = true;
+ done();
+ });
+ p.reject('error');
+
+ assert.ok(triggered);
});
- p.reject('error');
- assert.ok(!triggered);
- });
- });
+ it('does not trigger onRejected listeners if already fulfilled', function () {
+ var triggered = false;
- describe('#onRejected', function () {
- it('binds a listener to be fired on rejection', function (done) {
- var p = new Promise();
- p.reject('error');
+ p = Promise.of(1);
+ p.onRejected(function () {
+ triggered = true;
+ });
+ p.reject('error');
- p.onRejected(function (reason) {
- assert.equal('error', reason);
- done();
+ assert.ok(!triggered);
});
});
- });
- describe('#map', function () {
- it('yields the value of the promise', function (done) {
- p.map(function (x) {
- assert.equal('foo', x);
- done();
+ describe('#onRejected', function () {
+ it('binds a listener to be fired on rejection', function (done) {
+ p = new Promise();
+ p.reject('error');
+
+ p.onRejected(function (reason) {
+ assert.equal('error', reason);
+ done();
+ });
});
});
- it('yields the value after resolution', function (done) {
- p.map(function () {
- /* Promise is now resolved so map again... */
+ describe('#map', function () {
+ it('yields the value of the promise', function (done) {
p.map(function (x) {
assert.equal('foo', x);
done();
});
});
- });
-
- it('can be chained', function (done) {
- p.map(function (x) {
- return x + '!';
- }).map(function (y) {
- assert.equal('foo!', y);
- done();
- });
- });
- it('can be nested', function (done) {
- p.map(function (x) {
- p2.map(function (y) {
- p3.map(function (z) {
+ it('yields the value after resolution', function (done) {
+ p.map(function () {
+ /* Promise is now resolved so map again... */
+ p.map(function (x) {
assert.equal('foo', x);
- assert.equal('bar', y);
- assert.equal('baz', z);
done();
});
});
});
- });
- it('fulfils the identity property of a functor', function (done) {
- p.map(function (x) {
- return x;
- }).map(function (x) {
- assert.equal('foo', x);
- done();
+ it('can be chained', function (done) {
+ p.map(function (x) {
+ return x + '!';
+ }).map(function (y) {
+ assert.equal('foo!', y);
+ done();
+ });
});
- });
- it('fulfils the composition property of a functor #1', function (done) {
- var f = function (x) { return 'f(' + x + ')'; },
- g = function (x) { return 'g(' + x + ')'; };
+ it('can be nested', function (done) {
+ p.map(function (x) {
+ p2.map(function (y) {
+ p3.map(function (z) {
+ assert.equal('foo', x);
+ assert.equal('bar', y);
+ assert.equal('baz', z);
+ done();
+ });
+ });
+ });
+ });
- p.map(function (x) { return f(g(x)); }).map(function (x) {
- assert.equal('f(g(foo))', x);
- done();
+ it('fulfils the identity property of a functor', function (done) {
+ p.map(function (x) {
+ return x;
+ }).map(function (x) {
+ assert.equal('foo', x);
+ done();
+ });
});
- });
- it('fulfils the composition property of a functor #2', function (done) {
- var f = function (x) { return 'f(' + x + ')'; },
- g = function (x) { return 'g(' + x + ')'; };
+ it('fulfils the composition property of a functor #1', function (done) {
+ var f = function (x) { return 'f(' + x + ')'; },
+ g = function (x) { return 'g(' + x + ')'; };
- p.map(g).map(f).map(function (x) {
- assert.equal('f(g(foo))', x);
- done();
+ p.map(function (x) { return f(g(x)); }).map(function (x) {
+ assert.equal('f(g(foo))', x);
+ done();
+ });
});
- });
- });
- describe('#then', function () {
- it('yields its value like #map', function (done) {
- p.then(function (x) {
- assert.equal('foo', x);
- done();
+ it('fulfils the composition property of a functor #2', function (done) {
+ var f = function (x) { return 'f(' + x + ')'; },
+ g = function (x) { return 'g(' + x + ')'; };
+
+ p.map(g).map(f).map(function (x) {
+ assert.equal('f(g(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();
+ describe('#then', function () {
+ it('yields its value like #map', function (done) {
+ p.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('can be chained when returning a value', function (done) {
+ p.then(function (x) {
+ return x + '!';
+ }).then(function (x) {
+ assert.equal('foo!', x);
+ done();
+ });
});
- });
- it('always returns a promise', function () {
- assert.equal('object', typeof p.then());
- });
+ 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('returns a fulfilled promise with the return value of onRejected', function (done) {
- var p = new Promise(), p2;
+ it('always returns a promise', function () {
+ assert.equal('object', typeof p.then());
+ });
- p.reject('foo');
+ it('returns a fulfilled promise with the return value of onRejected', function (done) {
+ p = new Promise();
+ p.reject('foo');
- p2 = p.then(function () {
- return 1;
- }, function () {
- return 'error';
- });
+ p2 = p.then(function () {
+ return 1;
+ }, function () {
+ return 'error';
+ });
- p2.map(function (x) {
- assert.equal('error', x);
- assert.equal('fulfilled', p2.state());
- done();
+ 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'),
+ it('assumes the return value of onFulfilled', function (done) {
+ p = Promise.of('foo');
p2 = p.then(function () {
return 1;
}, function () {
return 'error';
});
- p2.map(function (x) {
- assert.equal(1, x);
- assert.equal('fulfilled', p2.state());
- done();
+ p2.map(function (x) {
+ assert.equal(1, x);
+ assert.equal('fulfilled', p2.state());
+ done();
+ });
});
});
- });
- describe('#concat', function () {
- it('fulfils the associativity property of semigroups #1', function (done) {
- var p = Promise.of([1]),
- p2 = Promise.of([2]),
+ describe('#concat', function () {
+ it('fulfils the associativity property of semigroups #1', function (done) {
+ p = Promise.of([1]);
+ p2 = Promise.of([2]);
p3 = Promise.of([3]);
- p.concat(p2).concat(p3).map(function (x) {
- assert.equal(1, x[0]);
- assert.equal(2, x[1]);
- assert.equal(3, x[2]);
- done();
+ p.concat(p2).concat(p3).map(function (x) {
+ assert.equal(1, x[0]);
+ assert.equal(2, x[1]);
+ assert.equal(3, x[2]);
+ done();
+ });
});
- });
- it('fulfils the associativity property of semigroups #2', function (done) {
- var p = Promise.of([1]),
- p2 = Promise.of([2]),
+ it('fulfils the associativity property of semigroups #2', function (done) {
+ p = Promise.of([1]);
+ p2 = Promise.of([2]);
p3 = Promise.of([3]);
- p.concat(p2.concat(p3)).map(function (x) {
- assert.equal(1, x[0]);
- assert.equal(2, x[1]);
- assert.equal(3, x[2]);
- done();
+ p.concat(p2.concat(p3)).map(function (x) {
+ assert.equal(1, x[0]);
+ assert.equal(2, x[1]);
+ assert.equal(3, x[2]);
+ done();
+ });
});
- });
- it('fulfils the identity of a semigroup', function (done) {
- var p = Promise.of([1]),
- p2 = Promise.of([2]),
+ it('fulfils the identity of a semigroup', function (done) {
+ p = Promise.of([1]);
+ p2 = Promise.of([2]);
p3 = Promise.of([3]);
- p.concat(p2).concat(p3).map(function (x) {
- return x;
- }).map(function (x) {
- assert.deepEqual([1, 2, 3], x);
- done();
+ p.concat(p2).concat(p3).map(function (x) {
+ return x;
+ }).map(function (x) {
+ assert.deepEqual([1, 2, 3], x);
+ done();
+ });
});
- });
- it('concatenates any monoid including strings', function (done) {
- p.concat(p2).concat(p3).map(function (x) {
- assert.equal('foobarbaz', x);
- done();
+ it('concatenates any monoid including strings', function (done) {
+ p.concat(p2).concat(p3).map(function (x) {
+ assert.equal('foobarbaz', x);
+ done();
+ });
});
- });
- it('is rejected if the first promise is rejected', function (done) {
- p.reject('Foo');
- p.concat(p2).onRejected(function (reason) {
- assert.equal('Foo', reason);
- done();
+ it('is rejected if the first promise is rejected', function (done) {
+ p.reject('Foo');
+ p.concat(p2).onRejected(function (reason) {
+ assert.equal('Foo', reason);
+ done();
+ });
});
- });
- it('is rejected if the second promise is rejected', function (done) {
- p2.reject('Foo');
- p.concat(p2).onRejected(function (reason) {
- assert.equal('Foo', reason);
- done();
+ it('is rejected if the second promise is rejected', function (done) {
+ p2.reject('Foo');
+ p.concat(p2).onRejected(function (reason) {
+ assert.equal('Foo', reason);
+ done();
+ });
});
- });
- it('takes the first rejection if both promises are rejected', function (done) {
- p.reject('Foo');
- p2.reject('Bar');
- p.concat(p2).onRejected(function (reason) {
- assert.equal('Foo', reason);
- done();
+ it('takes the first rejection if both promises are rejected', function (done) {
+ p.reject('Foo');
+ p2.reject('Bar');
+ p.concat(p2).onRejected(function (reason) {
+ assert.equal('Foo', reason);
+ done();
+ });
});
});
- });
- describe('#chain', function () {
- it('fulfils the associativity property of chain #1', function (done) {
- var f = function (x) { return Promise.of('f(' + x + ')'); },
- g = function (x) { return Promise.of('g(' + x + ')'); };
+ describe('#chain', function () {
+ it('fulfils the associativity property of chain #1', function (done) {
+ var f = function (x) { return Promise.of('f(' + x + ')'); },
+ g = function (x) { return Promise.of('g(' + x + ')'); };
- p.chain(f).chain(g).map(function (x) {
- assert.equal('g(f(foo))', x);
- done();
+ p.chain(f).chain(g).map(function (x) {
+ assert.equal('g(f(foo))', x);
+ done();
+ });
});
- });
- it('fulfils the associativity property of chain #2', function (done) {
- var f = function (x) { return Promise.of('f(' + x + ')'); },
- g = function (x) { return Promise.of('g(' + x + ')'); };
+ it('fulfils the associativity property of chain #2', function (done) {
+ var f = function (x) { return Promise.of('f(' + x + ')'); },
+ g = function (x) { return Promise.of('g(' + x + ')'); };
- p.chain(function (x) { return f(x).chain(g); }).map(function (x) {
- assert.equal('g(f(foo))', x);
- done();
+ p.chain(function (x) { return f(x).chain(g); }).map(function (x) {
+ assert.equal('g(f(foo))', x);
+ done();
+ });
});
});
- });
- describe('#ap', function () {
- it('fulfils the identity property of applicative', function (done) {
- Promise.of(function (a) { return a; }).ap(p).map(function (x) {
- assert.equal('foo', x);
- done();
+ describe('#ap', function () {
+ it('fulfils the identity property of applicative', function (done) {
+ Promise.of(function (a) { return a; }).ap(p).map(function (x) {
+ assert.equal('foo', x);
+ done();
+ });
});
- });
- it('fulfils the composition property of applicative #1', function (done) {
- var u = Promise.of(function (x) { return 'u(' + x + ')'; }),
- v = Promise.of(function (x) { return 'v(' + x + ')'; }),
- w = Promise.of('foo');
+ it('fulfils the composition property of applicative #1', function (done) {
+ var u = Promise.of(function (x) { return 'u(' + x + ')'; }),
+ v = Promise.of(function (x) { return 'v(' + x + ')'; }),
+ w = Promise.of('foo');
- Promise.of(function (f) {
- return function (g) {
- return function (x) {
- return f(g(x));
+ Promise.of(function (f) {
+ return function (g) {
+ return function (x) {
+ return f(g(x));
+ };
};
- };
- }).ap(u).ap(v).ap(w).map(function (x) {
- assert.equal('u(v(foo))', x);
- done();
+ }).ap(u).ap(v).ap(w).map(function (x) {
+ assert.equal('u(v(foo))', x);
+ done();
+ });
});
- });
- it('fulfils the composition property of applicative #2', function (done) {
- var u = Promise.of(function (x) { return 'u(' + x + ')'; }),
- v = Promise.of(function (x) { return 'v(' + x + ')'; }),
- w = Promise.of('foo');
+ it('fulfils the composition property of applicative #2', function (done) {
+ var u = Promise.of(function (x) { return 'u(' + x + ')'; }),
+ v = Promise.of(function (x) { return 'v(' + x + ')'; }),
+ w = Promise.of('foo');
- u.ap(v.ap(w)).map(function (x) {
- assert.equal('u(v(foo))', x);
- done();
+ u.ap(v.ap(w)).map(function (x) {
+ assert.equal('u(v(foo))', x);
+ done();
+ });
});
- });
- it('fulfils the homomorphism property of applicative #1', function (done) {
- var f = function (x) { return 'f(' + x + ')'; };
+ it('fulfils the homomorphism property of applicative #1', function (done) {
+ var f = function (x) { return 'f(' + x + ')'; };
- Promise.of(f).ap(Promise.of('foo')).map(function (x) {
- assert.equal('f(foo)', x);
- done();
+ Promise.of(f).ap(Promise.of('foo')).map(function (x) {
+ assert.equal('f(foo)', x);
+ done();
+ });
});
- });
- it('fulfils the homomorphism property of applicative #2', function (done) {
- var f = function (x) { return 'f(' + x + ')'; };
+ it('fulfils the homomorphism property of applicative #2', function (done) {
+ var f = function (x) { return 'f(' + x + ')'; };
- Promise.of(f('foo')).map(function (x) {
- assert.equal('f(foo)', x);
- done();
+ Promise.of(f('foo')).map(function (x) {
+ assert.equal('f(foo)', x);
+ done();
+ });
});
- });
- it('fulfils the interchange property of applicative #1', function (done) {
- var u = Promise.of(function (x) { return 'u(' + x + ')'; }),
- y = 'y';
+ it('fulfils the interchange property of applicative #1', function (done) {
+ var u = Promise.of(function (x) { return 'u(' + x + ')'; }),
+ y = 'y';
- u.ap(Promise.of(y)).map(function (x) {
- assert.equal('u(y)', x);
- done();
+ u.ap(Promise.of(y)).map(function (x) {
+ assert.equal('u(y)', x);
+ done();
+ });
});
- });
- it('fulfils the interchange property of applicative #2', function (done) {
- var u = Promise.of(function (x) { return 'u(' + x + ')'; }),
- y = 'y';
+ it('fulfils the interchange property of applicative #2', function (done) {
+ var u = Promise.of(function (x) { return 'u(' + x + ')'; }),
+ y = 'y';
- Promise.of(function (f) {
- return f(y);
- }).ap(u).map(function (x) {
- assert.equal('u(y)', x);
- done();
+ Promise.of(function (f) {
+ return f(y);
+ }).ap(u).map(function (x) {
+ assert.equal('u(y)', x);
+ done();
+ });
});
});
- });
- describe('#empty', function () {
- it('conforms to the right identity', function (done) {
- var p = Promise.of([1]);
+ describe('#empty', function () {
+ it('conforms to the right identity', function (done) {
+ p = Promise.of([1]);
- p.concat(p.empty()).map(function (x) {
- assert.deepEqual([1], x);
- done();
+ p.concat(p.empty()).map(function (x) {
+ assert.deepEqual([1], x);
+ done();
+ });
});
- });
- it('conforms to the left identity', function (done) {
- var p = Promise.of([1]);
+ it('conforms to the left identity', function (done) {
+ p = Promise.of([1]);
- p.empty().concat(p).map(function (x) {
- assert.deepEqual([1], x);
- done();
+ p.empty().concat(p).map(function (x) {
+ assert.deepEqual([1], x);
+ done();
+ });
});
});
- });
- describe('#conjoin', function () {
- it('concatenates values into a list regardless of type', function (done) {
- p.conjoin(p2).conjoin(p3).map(function (x) {
- assert.deepEqual(['foo', 'bar', 'baz'], x);
- done();
+ describe('#conjoin', function () {
+ it('concatenates values into a list regardless of type', function (done) {
+ p.conjoin(p2).conjoin(p3).map(function (x) {
+ assert.deepEqual(['foo', 'bar', 'baz'], x);
+ done();
+ });
});
- });
- it('concatenates values into a list even if already a list', function (done) {
- var p = Promise.of([1]),
- p2 = Promise.of([2, 3]),
+ it('concatenates values into a list even if already a list', function (done) {
+ p = Promise.of([1]);
+ p2 = Promise.of([2, 3]);
p3 = Promise.of([4]);
- p.conjoin(p2).conjoin(p3).map(function (x) {
- assert.deepEqual([1, 2, 3, 4], x);
- done();
+ p.conjoin(p2).conjoin(p3).map(function (x) {
+ assert.deepEqual([1, 2, 3, 4], x);
+ done();
+ });
});
- });
- it('concatenates values of mixed types', function (done) {
- var p2 = Promise.of([2, 3]);
+ it('concatenates values of mixed types', function (done) {
+ p2 = Promise.of([2, 3]);
- p.conjoin(p2).map(function (x) {
- assert.deepEqual(['foo', 2, 3], x);
- done();
+ p.conjoin(p2).map(function (x) {
+ assert.deepEqual(['foo', 2, 3], x);
+ done();
+ });
});
});
- });
- describe('#append', function () {
- it('appends promises to a promise of an array', function (done) {
- var p = Promise.of([1]),
+ describe('#append', function () {
+ it('appends promises to a promise of an array', function (done) {
+ p = Promise.of([1]);
p2 = Promise.of(2);
- p.append(p2).map(function (x) {
- assert.deepEqual([1, 2], x);
- done();
+ p.append(p2).map(function (x) {
+ assert.deepEqual([1, 2], x);
+ done();
+ });
});
- });
- it('appends promises of arrays to arrays without joining them', function (done) {
- var p = Promise.of([1]),
+ it('appends promises of arrays to arrays without joining them', function (done) {
+ p = Promise.of([1]);
p2 = Promise.of([2]);
- p.append(p2).map(function (x) {
- assert.deepEqual([1, [2]], x);
- done();
+ p.append(p2).map(function (x) {
+ assert.deepEqual([1, [2]], x);
+ done();
+ });
});
- });
- it('can be chained without nesting arrays', function (done) {
- var p = Promise.of([]),
- p2 = Promise.of([1]),
- p3 = Promise.of([2, 3]),
+ it('can be chained without nesting arrays', function (done) {
+ p = Promise.of([]);
+ p2 = Promise.of([1]);
+ p3 = Promise.of([2, 3]);
p4 = Promise.of([4]);
- p.append(p2).append(p3).append(p4).map(function (x) {
- assert.deepEqual([[1], [2, 3], [4]], x);
- done();
+ p.append(p2).append(p3).append(p4).map(function (x) {
+ assert.deepEqual([[1], [2, 3], [4]], x);
+ done();
+ });
});
});
- });
- describe('#spread', function () {
- it('calls the given function with each value of the Promise', function (done) {
- var p = Promise.of([1, 2, 3]);
+ describe('#spread', function () {
+ it('calls the given function with each value of the Promise', function (done) {
+ p = Promise.of([1, 2, 3]);
- p.spread(function (x, y, z) {
- assert.equal(1, x);
- assert.equal(2, y);
- assert.equal(3, z);
- done();
+ p.spread(function (x, y, z) {
+ assert.equal(1, x);
+ assert.equal(2, y);
+ assert.equal(3, z);
+ done();
+ });
});
- });
- it('returns a promise with a single value', function (done) {
- var p = Promise.of([1, 2, 3]);
+ it('returns a promise with a single value', function (done) {
+ p = Promise.of([1, 2, 3]);
- p.spread(function (x, y, z) {
- return x + y + z;
- }).map(function (x) {
- assert.equal(6, x);
- done();
+ p.spread(function (x, y, z) {
+ return x + y + z;
+ }).map(function (x) {
+ assert.equal(6, x);
+ done();
+ });
});
});
- });
- describe('#reduce', function () {
- it('returns a new promise with the result', function (done) {
- var p = Promise.of([[1], [2], [3]]);
+ describe('#reduce', function () {
+ it('returns a new promise with the result', function (done) {
+ p = Promise.of([[1], [2], [3]]);
- p.reduce(function (acc, e) {
- return acc.concat(e);
- }).map(function (x) {
- assert.deepEqual([1, 2, 3], x);
- done();
+ p.reduce(function (acc, e) {
+ return acc.concat(e);
+ }).map(function (x) {
+ assert.deepEqual([1, 2, 3], x);
+ done();
+ });
});
- });
- it('takes an optional initial value', function (done) {
- var p = Promise.of([1, 2, 3]);
+ it('takes an optional initial value', function (done) {
+ p = Promise.of([1, 2, 3]);
- p.reduce(function (acc, e) {
- return acc + e;
- }, 0).map(function (x) {
- assert.equal(6, x);
- done();
+ p.reduce(function (acc, e) {
+ return acc + e;
+ }, 0).map(function (x) {
+ assert.equal(6, x);
+ done();
+ });
});
});
});
- describe('Promises/A+ compliance', function () {
- require('promises-aplus-tests').mocha(adapter);
- });
-});
-
-describe('Array', function () {
- describe('.empty', function () {
- assert.deepEqual([], Array.empty());
+ describe('Array', function () {
+ describe('.empty', function () {
+ assert.deepEqual([], Array.empty());
+ });
});
-});
-describe('String', function () {
- describe('.empty', function () {
- assert.equal('', String.empty());
+ describe('String', function () {
+ describe('.empty', function () {
+ assert.equal('', String.empty());
+ });
});
-});
+}));
diff --git a/test/promises_aplus_test.js b/test/promises_aplus_test.js
new file mode 100644
index 0000000..716c59d
--- /dev/null
+++ b/test/promises_aplus_test.js
@@ -0,0 +1,8 @@
+/*global describe */
+'use strict';
+
+var adapter = require('./pacta_adapter');
+
+describe('Promises/A+ compliance', function () {
+ require('promises-aplus-tests').mocha(adapter);
+});