Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Created 'fate' package for a focused implementation of Promises/A

  • Loading branch information...
commit 8cccd309333b62ad2fc4bbb0aa263412933891c1 1 parent 386aa70
@shanewholloway authored
Showing with 462 additions and 3 deletions.
  1. +21 −0 LICENSE.txt
  2. +38 −3 README.md
  3. +205 −0 fate.js
  4. +18 −0 package.json
  5. +180 −0 testFate.js
View
21 LICENSE.txt
@@ -0,0 +1,21 @@
+Copyright (c) 2002-2012, TechGame Networks, LLC.
+All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
View
41 README.md
@@ -1,4 +1,39 @@
-fate
-====
+Fate is a small implementation of CommonJS Promises/A
+
+## Promises and Futures
+A `Promise` is an object with a `then(success, failure)` closure.
+
+A `Future` is a `Promise` that with `resolve(result)` and `reject(error)` closures.
+
+`thenable(thisArg, success, failure)` returns an unresolved `Future` instance bound to `success` and
+`failure` callbacks. `thenable()` is used to implement the semantics of other futures.
+
+`deferred(thisArg)` returns an unresolved `Future` instance.
+`resolved(result, thisArg)` returns a resolved `Future` instance.
+`rejected(error, thisArg)` returns a rejected `Future` instance.
+`inverted(aFuture)` returns a `Future` instance with `reject()` and `resolve()` transposed.
+
+`delay(ms)` returns a `deferred()` that will be answered after `ms` timeout.
+`timeout(target, ms)` returns a `delay(ms)` that will additionally be answered upon `target` being answered.
+
+## Compound Promises
+`each(anArray, thisArg)` is a compound `deferred()` answered after
+all promises in `anArray` are either rejected or resolved.
+Resolved if *all* promise are resolved from `anArray`..
+Rejected if *any* promises is rejected from `anArray`.
+
+`all(anArray, thisArg)` is a compound `deferred()` answered after
+all promises are resolved, or when any promise is rejected.
+Resolved if *any* promise is resolved from `anArray`.
+Rejected if *all* promises are rejected from `anArray`.
+
+`first(anArray, thisArg)` is a compound `deferred()` answered after
+any promise is either resolved or rejected.
+Resolved if *any* promise is resolved from `anArray`.
+Rejected if *all* promises are rejected from `anArray`.
+
+`any(anArray, thisArg)` is a compound `deferred()` answered after
+any promise is resolved, or when all are rejected.
+Resolved if *any* promise is resolved from `anArray`.
+Rejected if *all* promises are rejected from `anArray`.
-Futures, promises and deferreds
View
205 fate.js
@@ -0,0 +1,205 @@
+/* -*- coding: utf-8 -*- vim: set ts=2 sw=2 expandtab
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+~ Copyright (C) 2002-2012 TechGame Networks, LLC.
+~
+~ This library is free software; you can redistribute it
+~ and/or modify it under the terms of the MIT style License as
+~ found in the LICENSE file included with this distribution.
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+For futures and promises {then:, resolve:, reject:} must always be fully bound closures.
+*/
+
+"use strict";
+
+exports.Promise = Promise;
+function Promise(then) { if (then!==undefined) this.then = then }
+Promise.api = {
+ always: function(always) { return this.then(always, always); },
+ done: function(success) { return this.then(success, undefined); },
+ fail: function(failure) { return this.then(undefined, failure); },
+ timeout: function(ms) { return Promise.timeout(this, ms) } }
+Promise.prototype = Object.create(Promise.api,
+ {promise: {get: function(){return this}}});
+
+exports.isPromise = Promise.isPromise = isPromise;;
+function isPromise(tgt) {
+ return (tgt!==undefined) && (tgt.then!==undefined); }
+
+exports.asPromise = Promise.wrap = asPromise;
+function asPromise(tgt) {
+ return isPromise(tgt) ? tgt : Future.resolved(tgt).promise; }
+
+exports.when = Promise.when = when;
+function when(tgt, success, failure) {
+ if (success!==undefined || failure!==undefined)
+ return asPromise(tgt).then(success, failure)
+ else return asPromise(tgt); }
+
+exports.Future = Future;
+function Future(then, resolve, reject) {
+ this.promise = new Promise(then);
+ if (resolve !== undefined)
+ this.resolve = resolve;
+ if (reject !== undefined)
+ this.reject = reject;
+}
+Future.prototype = Object.create(Promise.api,
+ {then: {get: function(){return this.promise.then}}});
+Future.prototype.resolve = function(result) { }
+Future.prototype.reject = function(error) { }
+
+exports.isFuture = Future.isFuture = isFuture;
+function isFuture(tgt) {
+ return (tgt!==undefined) &&
+ ( (typeof tgt.resolve==='function')
+ || (typeof tgt.reject ==='function')); }
+
+
+//~ Future: thenable, deferred, resolved and rejected ~~~~~~~
+exports.thenable = Future.thenable = thenable
+function thenable(thisArg, success, failure, inner) {
+ if (typeof success === "object") {
+ if (failure===undefined)
+ failure = success.failure || success.reject || success.fail;
+ success = success.success || success.resolve || success.done;
+ }
+ if (success===undefined && success===undefined)
+ return Future.deferred(thisArg)
+ else return new Future(then, resolve, reject);
+
+ function then(success, failure) {
+ if (inner===undefined)
+ inner = Future.deferred(thisArg)
+ return inner.then(success, failure); }
+ function resolve(result) {
+ var next = (inner!==undefined) ? inner : Future.absentTail;
+ if (success!==undefined)
+ try {
+ var res = success.call(thisArg, result)
+ if (res!==undefined) result = res;
+ } catch (err) {
+ success = failure = undefined;
+ inner = Future.rejected(err);
+ return next.reject(err);
+ }
+ else if (failure===undefined) return;
+ success = failure = undefined;
+ inner = Future.resolved(result);
+ return next.resolve(result); }
+ function reject(error) {
+ var next = (inner!==undefined) ? inner : Future.absentTail;
+ if (failure!==undefined)
+ try {
+ var res = failure.call(thisArg, error)
+ if (res!==undefined) error = res;
+ } catch (err) { error = err; }
+ else if (success===undefined) return;
+ success = failure = undefined;
+ inner = Future.rejected(error);
+ return next.reject(error); } }
+
+Future.absentTail = {resolve: function(result) {}, reject: function(error) {}}
+Future.onActionError = function(error, thisArg) { console.error(error) }
+
+exports.deferred = Future.deferred = deferred;
+function deferred(thisArg) {
+ var actions=[], inner;
+ return new Future(then, resolve, reject);
+ function then(success, failure) {
+ if (inner!==undefined)
+ return inner.then(success, failure);
+ var ans = Future.thenable(thisArg, success, failure);
+ actions.push(ans);
+ return ans.promise; }
+ function resolve(result) {
+ if (actions===undefined) return;
+ inner = Future.resolved(result, thisArg);
+ for (var i=0; i<actions.length; i++) {
+ var ea = actions[i].resolve
+ if (ea===undefined) continue
+ try { ea.call(thisArg, result)
+ } catch (err) { Future.onActionError(err, thisArg) }
+ } actions = undefined; }
+ function reject(error) {
+ if (actions===undefined) return
+ inner = Future.rejected(error, thisArg);
+ for (var i=0; i<actions.length; i++) {
+ var ea = actions[i].reject
+ if (ea===undefined) continue
+ try { ea.call(thisArg, error)
+ } catch (err) { Future.onActionError(err, thisArg) }
+ } actions = undefined; }
+}
+
+exports.resolved = Future.resolved = resolved;
+function resolved(result, thisArg) {
+ return new Future(function then(success, failure) {
+ var ans = thenable(thisArg, success, failure)
+ return ans.resolve(result), ans.promise })}
+
+exports.rejected = Future.rejected = rejected;
+function rejected(error, thisArg) {
+ return new Future(function then(success, failure) {
+ var ans = thenable(thisArg, success, failure)
+ return ans.reject(error), ans.promise })}
+
+
+//~ Utility Futures: invert, delay and timeout ~~~~~~~~~~~~~~
+exports.inverted = Future.inverted = inverted;
+function inverted(tgt) {
+ if (tgt===undefined) tgt = deferred();
+ return new Future(tgt.then, tgt.reject, tgt.resolve) };
+
+exports.delay = Future.delay = Promise.delay = delay;
+function delay(ms, bReject) {
+ var res=deferred(),
+ tid=setTimeout(bReject?res.reject:res.resolve, ms);
+ res.always(function() {clearTimeout(tid)});
+ return res; }
+exports.timeout = Future.timeout = Promise.timeout = timeout;
+function timeout(target, ms, bReject) {
+ var res = delay(ms, (bReject!=undefined?bReject:true));
+ when(target, res);
+ return res; }
+
+
+//~ Compositions: any, all, every, first ~~~~~~~~~~~~~~~~~~~~
+function forEachPromise(anArray, thisArg, resolveFirst, rejectFirst, rejectAll) {
+ var n=0, future=deferred(thisArg), linchpin={
+ push: function(ea) {
+ if (isPromise(ea)) {
+ ++n; ea.then(linchpin)
+ } else if (resolveFirst)
+ future.resolve(anArray)
+ return ea },
+ resolve: function() {
+ if (resolveFirst || (--n < 1))
+ future.resolve(anArray) },
+ reject: function() {
+ if (rejectFirst || (--n < 1))
+ future.reject(anArray);
+ if (rejectAll)
+ future.resolve = future.reject;} };
+
+ [].forEach.call(anArray, linchpin.push)
+ if (n<1) future.resolve(n);
+ return future; }
+
+exports.every = Future.every = Promise.every = every;
+function every(anArray, thisArg) {
+ if (arguments.length>1) anArray = [].slice.call(arguments);
+ return forEachPromise(anArray, thisArg, false, false, true) }
+exports.all = Future.all = Promise.all = all;
+function all(anArray, thisArg) {
+ if (arguments.length>1) anArray = [].slice.call(arguments);
+ return forEachPromise(anArray, thisArg, false, true) }
+exports.first = Future.first = Promise.first = first;
+function first(anArray, thisArg) {
+ if (arguments.length>1) anArray = [].slice.call(arguments);
+ return forEachPromise(anArray, thisArg, true, true) }
+exports.any = Future.any = Promise.any = any;
+function any(anArray, thisArg) {
+ if (arguments.length>1) anArray = [].slice.call(arguments);
+ return forEachPromise(anArray, thisArg, true, false) }
+
View
18 package.json
@@ -0,0 +1,18 @@
+{
+ "name": "fate",
+ "version": "0.0.1",
+ "license": "MIT",
+ "description": "Futures, Promises and Deferreds",
+ "keywords": [ "future", "promise", "deferred" ],
+ "author": "Shane Holloway <shane@techgame.net>",
+ "readmeFilename": "README.md",
+
+ "repository": {
+ "type":"git",
+ "url": "https://github.com/shanewholloway/fate.git"},
+
+ "main": "fate.js",
+ "scripts": {
+ "test": "node testFate.js"
+ }
+}
View
180 testFate.js
@@ -0,0 +1,180 @@
+"use strict";
+
+function say(msg, ans) {
+ if (ans===undefined)
+ return console.log(' ', msg)
+ else return console.log(' ', msg, ans)}
+function fail(msg, ans) {
+ var err = new Error('ErrorObj::'+msg);
+ console.log(' ', "RAISING ERROR", err, ans);
+ throw err; }
+
+var p = require('./fate.js');
+
+function testFuture(aVar) {
+ var n,p
+ n = aVar.then(function(a){say('resolve 1',a)}, function(a){say('REJECT 1',a)})
+ n = n.then(function(a){say('resolve 2',a)}, function(a){say('REJECT 2',a)})
+ p = n
+ p.then(function(a){say('pre resolve 3',a)}, function(a){say('pre REJECT 3',a)})
+ n = n.then(function(a){fail('resolve 3',a)}, function(a){fail('REJECT 3',a)})
+ p.then(function(a){say('parallel resolve 3',a)}, function(a){say('parallel REJECT 3',a)})
+
+ n.then(function(a){say('resolve 4',a)}, function(a){say('REJECT 4',a)})
+ n.then(function(a){say('parallel resolve 4',a)}, function(a){say('parallel REJECT 4',a)})
+
+ return aVar;
+}
+
+if (1) {
+ console.log("\nResolved:");
+ testFuture(p.resolved({state: "awesome"}))
+ console.log("\n post Resolved:");
+}
+
+if (1) {
+ console.log("\nRejected:");
+ testFuture(p.rejected({state: "dejected"}))
+ console.log("\n post Rejected:");
+}
+
+if (1) {
+ console.log("\nDeferred Resolved:");
+ var d = testFuture(p.deferred());
+
+ console.log("\n resolving deferred:");
+ d.resolve({state: "passed"});
+
+ console.log("\n post resolving deferred:");
+}
+
+if (1) {
+ console.log("\nDeferred Rejected:");
+ var d = testFuture(p.deferred());
+
+ console.log("\n rejecting deferred:");
+ d.reject({state: "failed"});
+
+ console.log("\n post rejecting deferred:");
+}
+
+if (1) {
+ console.log("\nInverted Resolved:");
+ var d = testFuture(p.inverted());
+
+ console.log("\n resolving deferred:");
+ d.resolve({state: "invert +++"});
+
+ console.log("\n post resolving deferred:");
+}
+
+if (1) {
+ console.log("\nInverted Rejected:");
+ var d = testFuture(p.inverted());
+
+ console.log("\n rejecting deferred:");
+ d.reject({state: "invert ---"});
+
+ console.log("\n post rejecting deferred:");
+}
+
+
+function futureArray(n) {
+ var res = new Array(n||3);
+ for (var i=0; i<res.length; i++) {
+ res[i] = p.deferred(this);
+ res[i].idx = i;
+ }
+ return res;
+}
+
+if (1) {
+ var fa = futureArray(3)
+ console.log("\nResolve All:");
+
+ console.log(' PRE all')
+ p.all(fa).then(
+ function(a){say('all resolve')},
+ function(a){say('all reject')})
+ console.log(' POST all\n')
+
+ fa[0].resolve(1942)
+ console.log(' POST 1942')
+ fa[1].resolve(2042)
+ console.log(' POST 2042')
+ fa[2].resolve(1911)
+ console.log(' POST 1911')
+
+ console.log("\n post Resolve All:");
+}
+
+if (1) {
+ var fa = futureArray(3)
+ console.log("\nResolve Every:");
+
+ console.log(' PRE every')
+ p.every(fa).then({
+ resolve: function(a){say('every resolve')},
+ reject: function(a){say('every reject')}})
+ console.log(' POST every\n')
+
+ fa[0].resolve(1942)
+ console.log(' POST 1942')
+ fa[1].reject(2042)
+ console.log(' POST 2042')
+ fa[2].resolve(1911)
+ console.log(' POST 1911')
+
+ console.log("\n post Resolve Every:");
+}
+if (1) {
+ var fa = futureArray(3)
+ console.log("\nResolve Any:");
+
+ console.log(' PRE any')
+ p.any(fa).then(
+ function(a){say('any resolve')},
+ function(a){say('any reject')})
+ console.log(' POST any\n')
+
+ fa[0].reject(1942)
+ console.log(' POST 1942')
+ fa[1].resolve(2042)
+ console.log(' POST 2042')
+ fa[2].reject(1911)
+ console.log(' POST 1911')
+
+ console.log("\n post Resolve Any:");
+}
+
+if (1) {
+ var fa = futureArray(3)
+ console.log("\nResolve First:");
+
+ console.log(' PRE first')
+ p.first(fa).then({
+ resolve: function(a){say('first resolve')},
+ reject: function(a){say('first reject')}})
+ console.log(' POST first\n')
+
+ fa[0].reject(1942)
+ console.log(' POST 1942')
+ fa[1].resolve(2042)
+ console.log(' POST 2042')
+ fa[2].reject(1911)
+ console.log(' POST 1911')
+
+ console.log("\n post Resolve First:");
+}
+
+if (0) {
+ var f0=p.deferred(), f1=p.deferred();
+ f0.timeout(200).then(
+ function(){ say('success f0') },
+ function(){ say('fail f0') })
+ setTimeout(function() {f0.resolve(1942)}, 50)
+
+ f1.timeout(200).then(
+ function(){ say('fail f1') },
+ function(){ say('success f1') })
+}
Please sign in to comment.
Something went wrong with that request. Please try again.