/*global exports, define, process */
(function (root, createModule) {
"use strict";
var
Tillthen v0.3.5
https://github.com/biril/tillthen
Licensed and freely distributed under the MIT License
Copyright (c) 2013-2015 Alex Lambiris
/*global exports, define, process */
(function (root, createModule) {
"use strict";
var
Package various utility functions we’ll be reusing
_ = {
isObject: function (o) {
return Object.prototype.toString.call(o) === "[object Object]";
},
isFunction: function (f) {
return Object.prototype.toString.call(f) === "[object Function]";
}
},
Detect the current environment (can be CommonJS, AMD or browser). Tillthen will be exposed as a module or global depending on that
env = (function () {
A global define
method with an amd
property signifies the presence of an AMD
loader (require.js, curl.js)
if (typeof define === "function" && define.amd) { return "AMD"; }
A global exports
object signifies CommonJS-like enviroments that support
module.exports
, e.g. Node
if (typeof exports !== "undefined" && _.isObject(exports)) { return "CommonJS"; }
If none of the above, then assume a browser, without AMD
return "browser";
}());
Create a next-turn-evaluation function: A function that evaluates given function f
on
given value v
, soon, i.e. not in the same turn of the event loop
(Note that support for CommonJS will be specific to node. So if the detected environment
is in fact ‘CommonJS’, the presense of node’s process
object is assumed and the latter
is used to get a reference to Node’s nextTick
method)
_.evaluateOnNextTurn = (function () {
return env === "CommonJS" ?
function (f /*, arg1, .., argN */) {
var args = Array.prototype.slice.apply(arguments);
args.shift();
process.nextTick(function () { f.apply(null, args); });
} :
function (/* f, arg1, .., argN */) {
Array.prototype.splice.call(arguments, 1, 0, 0);
root.setTimeout.apply(root, arguments);
};
}());
Expose as a module or global depending on the detected environment
switch (env) {
case "CommonJS":
createModule(_, exports);
break;
case "AMD":
define(["exports"], function (exports) { return createModule(_, exports); });
break;
case "browser":
root.tillthen = createModule(_, {});
When running in a browser (without AMD modules), attach a noConflict
onto the
tillthen
global
root.tillthen.noConflict = (function () {
Save a reference to the previous value of ‘tillthen’, so that it can be restored later on, if ‘noConflict’ is used
var previousTillthen = root.tillthen;
Run in no-conflict mode, setting the tillthen
global to to its previous value.
Returns tillthen
return function () {
var tillthen = root.tillthen;
root.tillthen = previousTillthen;
tillthen.noConflict = function () { return tillthen; };
return tillthen;
};
}());
}
}(this, function (_, tillthen) {
"use strict";
var
Tillthen deferred constructor
TillthenDeferred = function () {},
Tillthen promise constructor
TillthenPromise = function () {},
Resolve deferred
, i.e. transition it to an appropriate state depending on given x
resolveDeferred = function (deferred, x) {
var xThen = null;
If promise
and x
refer to the same object, reject promise with a TypeError as
the reason
if (deferred.promise === x) {
return deferred.reject(new TypeError("Cannot resolve a promise with itself"));
}
If x
is a promise, adopt its (future) state
if (x instanceof TillthenPromise) {
if (x.state === "fulfilled") { return deferred.fulfill(x.result); }
if (x.state === "rejected") { return deferred.reject(x.result); }
return x.then(deferred.fulfill, deferred.reject);
}
if x
is not a thenable, fulfill promise with x
. If attempting to query then
throws an error, reject promise with that error as the reason
(The procedure of first storing a reference to x.then
, then testing that reference,
and then calling that reference, avoids multiple accesses to the x.then
property
ensuring consistency in the face of an accessor property, whose value could change
between retrievals)
try {
if (!(_.isObject(x) || _.isFunction(x)) || !_.isFunction(xThen = x.then)) {
return deferred.fulfill(x);
}
}
catch (error) { deferred.reject(error); }
If x
is a thenable adopt its (future) state
xThen.call(x, function (value) {
resolveDeferred(deferred, value);
}, function (reason) {
deferred.reject(reason);
});
},
Create an evaluator for given onResulted
function and deferred
object. When invoked
with a result
(value or reason), the evaluator will evaluate onResulted(result)
and will use the returned value to resolve deferred
createEvaluator = function (onResulted, deferred) {
return function (result) {
try { resolveDeferred(deferred, onResulted(result)); }
catch (reason) { deferred.reject(reason); }
};
},
Create a deferred object: A pending promise with resolve
, fulfill
and reject
methods
createDeferred = function () {
var
Promise’s current state
state = "pending",
Value of fulfillment or reason of rejection. Will be set when fulfillment or rejection actually occurs
result,
Queues of fulfillment / rejection handlers. Handlers are added whenever the
promise’s then
method is invoked
fulfillQueue = [],
rejectQueue = [],
The actual promise. The deferred will derive from this
promise = new TillthenPromise(),
The deferred to be returned
deferred = null,
Queue a handler and a dependant deferred for fulfillment. When (and if) the promise is fulfilled, the handler will be evaluated on promise’s value and the result will be used to resolve the dependant deferred
queueForFulfillment = function (onFulfilled, dependantDeferred) {
If the promise is already rejected, there’s nothing to be done
if (state === "rejected") { return; }
If given onFulfilled
is not a function then use a pass-through function in
its place
_.isFunction(onFulfilled) || (onFulfilled = function (value) { return value; });
Create an evaluator to do the dirty work and either run it ‘now’ if the promise is already fulfilled or as soon as (and if) that eventually happens
var evaluator = createEvaluator(onFulfilled, dependantDeferred);
state === "fulfilled" ? _.evaluateOnNextTurn(evaluator, result) :
fulfillQueue.push(evaluator);
},
Queue a handler and a dependant deferred for rejection. When (and if) the promise is rejected, the handler will be evaluated on promise’s reason and the result will be used to resolve the dependant deferred
queueForRejection = function (onRejected, dependantDeferred) {
If the promise is already fulfilled, there’s nothing to be done
if (state === "fulfilled") { return; }
If given onRejected
is not a function then use a pass-through function in
its place
_.isFunction(onRejected) || (onRejected = function (error) { throw error; });
Create an evaluator to do the dirty work and either run it ‘now’ if the promise is already rejected or as soon as (and if) that eventually happens
var evaluator = createEvaluator(onRejected, dependantDeferred);
state === "rejected" ? _.evaluateOnNextTurn(evaluator, result) :
rejectQueue.push(evaluator);
},
Fulfil the promise. Will run the queued fulfillment-handlers and resolve
dependant promises. Note that the fulfill
method will be exposed on the
returned deferred only - not on any returned promise: not by the deferred’s
underlying promise or those returned by invoking then
fulfill = function (value) {
Dont fulfill the promise unless it’s currently in a pending state
if (state !== "pending") { return; }
Fulfil the promise
state = "fulfilled";
_.evaluateOnNextTurn(function (fq) {
for (var i = 0, l = fq.length; i < l; ++i) { fq[i](value); }
}, fulfillQueue);
fulfillQueue = [];
result = value;
return promise;
},
Reject the promise. Will run the queued rejection-handlers and resolve
dependant promises. As with the fulfill
method, the reject
method will be
exposed on the returned deferred only - not on any returned promise
reject = function (reason) {
Dont reject the promise unless it’s currently in a pending state
if (state !== "pending") { return; }
Reject the promise
state = "rejected";
_.evaluateOnNextTurn(function (rq) {
for (var i = 0, l = rq.length; i < l; ++i) { rq[i](reason); }
}, rejectQueue);
rejectQueue = [];
result = reason;
return promise;
};
Attach then
method as well as state
and result
getters to the promise:
Object.defineProperties(promise, {
Access the promise’s current or eventual fulfillment value or rejection reason.
As soon as (if ever) the promise is fulfilled, the onFulfilled
handler will
be evaluated on the promise’s fulfillment value. Similarly, as soon as (if ever)
the promise is rejected, the onRejected
handler will be evaluated on the
rejection reason. Returns a new promise which will be eventually resolved
with the value / reason / promise returned by onFulfilled
or onRejected
then: {
value: function (onFulfilled, onRejected) {
Create a new deferred, one which is dependant on (and will be resolved
with) the the value / reason / promise returned by onFulfilled
or
onRejected
var dependantDeferred = createDeferred();
Queue onFulfilled
and onRejected
for evaluation upon the promise’s
eventual fulfillment or rejection
queueForFulfillment(onFulfilled, dependantDeferred);
queueForRejection(onRejected, dependantDeferred);
Return the dependant deferred’s underlying promise
return dependantDeferred.promise;
}
},
Get the promise’s current state (‘pending’, ‘fulfilled’ or ‘rejected’)
state: { get: function () { return state; } },
Get the promise’s result (value or reason). Valid only after the promise has either been fulfilled or rejected
result: { get: function () { return result; } }
});
Derive a deferred from the promise, attach needed methods and return it
TillthenDeferred.prototype = promise;
deferred = new TillthenDeferred();
Object.defineProperties(deferred, {
Get the deferred’s underlying promise
promise: { get: function () { return promise; } },
Fulfill the promise with given value
fulfill: { value: fulfill },
Reject the promise with given reason
reject: { value: reject },
Resolve the promise with given result
: Fulfill it if result
is a value, or
cause it to assume result
‘s (future) state if it’s a promise itself
resolve: { value: function (result) { resolveDeferred(this, result); } }
});
return deferred;
};
Attach the defer
/ getVersion
methods to Tillthen and return it
Object.defineProperties(tillthen, {
Get a deferred object: A pending promise with resolve
, fulfill
and reject
methods
defer: { value: createDeferred },
Get current version of Tillthen
version: { get: function () { return "0.3.5"; } }
});
return tillthen;
}));