Last active
December 30, 2015 01:59
-
-
Save warmwaffles/7759599 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
/** | |
* Block class is used for routing errors to higher level logic. | |
*/ | |
function Block(errback) { | |
this._parent = Block.current; | |
this._errback = errback; | |
} | |
Block.current = null; | |
/** | |
* Wrap a function such that any exceptions it generates are sent to the error | |
* callback of the Block that is active at the time of the call to guard(). | |
* If no Block is active, just returns the function. | |
* | |
* Example: stream.on('end', Block.guard(function() { ... })); | |
*/ | |
Block.guard = function (func) { | |
if (this.current) { | |
return this.current.guard(func); | |
} | |
return func; | |
}; | |
/** | |
* Begins a new Block with two callback functions. The first is the main part | |
* of the block (think 'try body'), the second is the rescue function/error | |
* callback (think 'catch'). The terminology follows Ruby for no other reason | |
* than that Block, begin and rescue describe an exception handling paradigm | |
* and are not reserved words in JavaScript. | |
*/ | |
Block.begin = function (block, rescue) { | |
return new Block(rescue).trap(block); | |
}; | |
/** | |
* Returns a function(err) that can be invoked at any time to raise an | |
* exception against the now current block (or the current context if no | |
* current). Errors are only raised if the err argument is true so this can be | |
* used in both error callbacks and error events. | |
* | |
* Example: request.on('error', Block.errorHandler()) | |
*/ | |
Block.errorHandler = function () { | |
// Capture the now current Block for later | |
var current = this.current; | |
return function (err) { | |
if (!err) { | |
return false; | |
} | |
if (current) { | |
return current.raise(err); | |
} | |
throw err; | |
}; | |
}; | |
/** | |
* Raises an exception on the Block. If the block has an error callback, it is | |
* given the exception. Otherwise, raise(...) is called on the parent block. | |
* If there is no parent, the exception is simply raised. | |
* | |
* Any nested exceptions from error callbacks will be raised on the block's | |
* parent. | |
*/ | |
Block.prototype.raise = function (err) { | |
if (this._errback) { | |
try { | |
this._errback(err); | |
} catch (nestedE) { | |
if (this._parent) { | |
this._parent.raise(nestedE); | |
} else { | |
throw nestedE; | |
} | |
} | |
} else { | |
if (this._parent) { | |
this._parent.raise(err); | |
} else { | |
throw err; | |
} | |
} | |
}; | |
/** | |
* Executes a callback in the context of this block. Any | |
* errors will be passed to this Block's raise() method. | |
* Returns the value of the callback or undefined on error. | |
*/ | |
Block.prototype.trap = function (callback) { | |
var origCurrent = Block.current, | |
ret; | |
Block.current = this; | |
try { | |
ret = callback(); | |
Block.current = origCurrent; | |
return ret; | |
} catch (e) { | |
Block.current = origCurrent; | |
this.raise(e); | |
return undefined; | |
} | |
}; | |
/** | |
* Wraps a function and returns a function that routes | |
* errors to this block. This is similar to trap but | |
* returns a new function instead of invoking the callback | |
* immediately. | |
*/ | |
Block.prototype.guard = function (func) { | |
if (func.__guarded__) { | |
return func; | |
} | |
var self = this, | |
wrapped = function () { | |
var origCurrent = Block.current, | |
ret; | |
Block.current = self; | |
try { | |
ret = func.apply(this, arguments); | |
Block.current = origCurrent; | |
return ret; | |
} catch (e) { | |
Block.current = origCurrent; | |
self.raise(e); | |
return undefined; | |
} | |
}; | |
wrapped.__guarded__ = true; | |
return wrapped; | |
}; | |
module.exports = Block; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
var PENDING = 1, | |
RESOLVED = 2, | |
REJECTED = 3, | |
slice = Array.prototype.slice; | |
/** | |
* Call all the functions in a list. | |
* | |
* @private | |
* @param {function|function[]} list List of callbacks | |
* @param {Object} context Object for 'this' inside callbacks | |
* @param {Array} args Arguments array | |
*/ | |
function call(list, context, args) { | |
var i, newArg; | |
if (typeof list === 'function') { | |
newArg = list.apply(context, args); | |
if (newArg !== 'undefined') { | |
args[0] = newArg; | |
} | |
} else if (Array.isArray(list)) { | |
for (i = 0; i < list.length; i += 1) { | |
call(list[i], context, args); | |
} | |
} | |
} | |
/** | |
* Deferred object constructor. | |
* | |
* @constructor | |
*/ | |
function Deferred() { | |
this.state = PENDING; | |
this.context = null; | |
this.args = null; | |
this.events = { | |
done: [], | |
fail: [], | |
always: [] | |
}; | |
} | |
Deferred.PENDING = PENDING; | |
Deferred.RESOLVED = RESOLVED; | |
Deferred.REJECTED = REJECTED; | |
/** | |
* Register a callback that will be fired when the process is resolved. | |
* | |
* @param {function|function[]} callback | |
* @returns {Deferred} | |
*/ | |
Deferred.prototype.done = function (callback) { | |
// Fire the event straight away if resolved | |
if (this.state === RESOLVED) { | |
call(callback, this.context, this.args); | |
} else if (this.state === PENDING) { | |
this.events.done.push(callback); | |
} | |
return this; | |
}; | |
/** | |
* Register a callback that will be fired when the process fails. | |
* | |
* @param {function|function[]|Deferred} callback | |
* @returns {Deferred} | |
*/ | |
Deferred.prototype.fail = function (callback) { | |
// Fire the event straight away if rejected | |
if (this.state === REJECTED) { | |
call(callback, this.context, this.args); | |
} else if (this.state === PENDING) { | |
this.events.fail.push(callback); | |
} | |
return this; | |
}; | |
/** | |
* Register a callback that will be fired whatever the result. | |
* | |
* @param {function|function[]} callback | |
* @returns {Deferred} | |
*/ | |
Deferred.prototype.always = function (callback) { | |
// Fire the event straight away if not pending | |
if (this.state !== PENDING) { | |
call(callback, this.context, this.args); | |
} else { | |
this.events.always.push(callback); | |
} | |
return this; | |
}; | |
/** | |
* Resolve the deferred object and fire all the done and always callbacks, in that order. | |
* All arguments are passed to the callbacks. | |
* | |
* @returns {Deferred} | |
*/ | |
Deferred.prototype.resolve = function () { | |
var done, always; | |
if (this.state === PENDING) { | |
this.state = RESOLVED; | |
this.args = arguments; | |
done = this.events.done; | |
always = this.events.always; | |
// Purge all callbacks | |
this.events = null; | |
call(done, this.context, this.args); | |
call(always, this.context, this.args); | |
} | |
return this; | |
}; | |
/** | |
* Alias for Deferred#resolve, but with the first argument used as the callback context (this). | |
* | |
* @param {Object} context Context for the callbacks | |
* @returns {Deferred} | |
*/ | |
Deferred.prototype.resolveWith = function (context) { | |
if (this.state === PENDING) { | |
this.context = context; | |
this.resolve.apply(this, slice.call(arguments, 1)); | |
} | |
return this; | |
}; | |
/** | |
* Register a callback that will be fired when the process is rejected. All | |
* arguments are passed to the callbacks. | |
* | |
* @returns {Deferred} | |
*/ | |
Deferred.prototype.rejectWith = function () { | |
var fail, always; | |
if (this.state === PENDING) { | |
this.state = REJECTED; | |
this.args = arguments; | |
fail = this.events.fail; | |
always = this.events.always; | |
// Purge all callbacks | |
this.events = null; | |
call(fail, this.context, this.args); | |
call(always, this.context, this.args); | |
} | |
return this; | |
}; | |
/** | |
* Alias for Deferred#reject, but with the first argument used as the callback context (this). | |
* | |
* @param {Object} context Context for the callbacks | |
* @returns {Deferred} | |
*/ | |
Deferred.prototype.reject = function (context) { | |
if (this.state === PENDING) { | |
this.context = context; | |
this.reject.apply(this, slice.call(arguments, 1)); | |
} | |
return this; | |
}; | |
module.exports = Deferred; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
var Block = require('./block'); | |
/** | |
* A Future class as per the literature on the topic. The two main operations | |
* are force() and resolve(). | |
*/ | |
function Future(resolution) { | |
this._resolved = false; | |
this._resolution = null; | |
this._pending = null; | |
if (arguments.length > 0) { | |
this.resolve(resolution); | |
} | |
} | |
/** | |
* Cast an arbitrary value to a Future. If already a Future, just return it. | |
* Otherwise, return a new Future resolved with the value. | |
*/ | |
Future.cast = function (futureOrLiteral) { | |
if (futureOrLiteral instanceof Future) { | |
return futureOrLiteral; | |
} | |
return new Future(futureOrLiteral); | |
}; | |
Future.prototype = {}; | |
/** | |
* Invokes the callback(result) upon resolution of the future. Return true if | |
* force executed immediately, false if pended. | |
* | |
* If the callback is pended, it is wrapped with Block.guard so that any | |
* exceptions it throws are routed to the Block in effect at the time of the | |
* call to force(). | |
*/ | |
Future.prototype.force = function (callback) { | |
var pended, ret = false; | |
if (this._resolved) { | |
if (callback) { | |
callback(this._resolution); | |
} | |
ret = true; | |
} else if (callback) { | |
pended = Block.guard(callback); | |
if (!this._pending) { | |
this._pending = [pended]; | |
} else { | |
this._pending.push(pended); | |
} | |
} | |
return ret; | |
}; | |
/** | |
* Resolves the future. Any pended force callbacks are executed immediately. | |
* Any future calls to force() will invoke their callbacks immediately. | |
*/ | |
Future.prototype.resolve = function (resolution) { | |
if (this._resolved) { | |
throw new Error('Logic error. Future resolved multiple times.'); | |
} | |
this._resolved = true; | |
this._resolution = resolution; | |
if (this._pending) { | |
this._pending.forEach(function (pended) { | |
pended(resolution); | |
}); | |
} | |
}; | |
/** | |
* Return a new Future whose resolution is dependent on the resolution of this | |
* future. When this future is resolved, the transformer callback will be | |
* invoked with its resolution and the callback result will become the | |
* resolution of the new Future returned by this method. | |
*/ | |
Future.prototype.chain = function (transformer) { | |
var chained = new Future(); | |
this.force(function (resolution) { | |
chained.resolve(transformer(resolution)); | |
}); | |
return chained; | |
}; | |
/** | |
* Forward the resolution from this future to another future. This future is | |
* forced and the resolution is resolved on the passed future. | |
*/ | |
Future.prototype.forward = function (otherFuture) { | |
this.force(function (resolution) { | |
otherFuture.resolve(resolution); | |
}); | |
}; | |
module.exports = Future; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment