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); +});