node-switchback
Advanced tools
Comparing version 0.0.4 to 0.1.0
/** | ||
* Module dependencies | ||
*/ | ||
var util = require('util'); | ||
var _ = require('lodash'); | ||
var util = require('util'); | ||
var constants = require('./constants'); | ||
/** | ||
* factory | ||
* | ||
* | ||
* @return {Switchback} | ||
* | ||
* | ||
* An anonymous function is used as the base for switchbacks so that | ||
* they are both dereferenceable AND callable. This allows functions | ||
* which accept switchback definitions to maintain compatibility with | ||
* which accept switchback definitions to maintain compatibility with | ||
* standard node callback conventions (which are better for many situations). | ||
* | ||
* | ||
* This also means that instantiated switchbacks may be passed interchangably | ||
@@ -24,20 +25,19 @@ * into functions expecting traditional node callbacks, and everything will | ||
module.exports = function (callbackContext) { | ||
module.exports = function(callbackContext) { | ||
var _switch = function( /* err, arg1, arg2, ..., argN */ ) { | ||
var args = Array.prototype.slice.call(arguments); | ||
var _switch = function( /* err, arg1, arg2, ..., argN */ ) { | ||
var args = Array.prototype.slice.call(arguments); | ||
// Trigger error handler | ||
var err = args[0]; | ||
if (err) { | ||
return _switch.error.apply(callbackContext || this, args); | ||
} | ||
return _switch.success.apply(callbackContext || this, args.slice(1)); | ||
}; | ||
// Trigger error handler | ||
var err = args[0]; | ||
if (err) { | ||
return _switch.error.apply(callbackContext || this, args); | ||
} | ||
return _switch.success.apply(callbackContext || this, args.slice(1)); | ||
}; | ||
// Mark switchback function so it can be identified for tests | ||
_switch[constants.telltale.key] = constants.telltale.value; | ||
// Mark switchback function so it can be identified for tests | ||
_switch[constants.telltale.key] = constants.telltale.value; | ||
return _switch; | ||
}; | ||
return _switch; | ||
}; |
153
lib/index.js
@@ -11,2 +11,3 @@ /** | ||
var constants = require('./constants'); | ||
var EventEmitter = require('events').EventEmitter; | ||
@@ -30,80 +31,100 @@ | ||
var switchback = function ( callback, defaultHandlers, callbackContext ) { | ||
var switchback = function(callback, defaultHandlers, callbackContext) { | ||
// If callback is not a function or an object, I don't know wtf it is, | ||
// so let's just return early before anything bad happens, hmm? | ||
if (!_.isObject(callback) && !_.isFunction(callback)) { | ||
return callback; | ||
} | ||
// Build switchback | ||
var Switchback = factory(callbackContext); | ||
var Switchback = factory(callbackContext); | ||
// If callback is not a function or an object, I don't know wtf it is, | ||
// so let's just return early before anything bad happens, hmm? | ||
if (!_.isObject(callback) && !_.isFunction(callback)) { | ||
// Actually let's not. | ||
// Instead, make the new switchback an EventEmitter | ||
var e = new EventEmitter(); | ||
Switchback.emit = function() { | ||
return e.emit.apply(e, Array.prototype.slice.call(arguments)); | ||
}; | ||
Switchback.on = function(evName, handler) { | ||
return e.on.apply(e, Array.prototype.slice.call(arguments)); | ||
}; | ||
// Normalize `callback` to a switchback definition object. | ||
callback = normalize.callback(callback); | ||
// Then emit the appropriate event when the switchback is triggered. | ||
callback = { | ||
error: function (err) { | ||
Switchback.emit('error', err); | ||
}, | ||
success: function (/*...*/) { | ||
Switchback.emit.apply(e, ['success'].concat(Array.prototype.slice.call(arguments))); | ||
} | ||
}; | ||
} | ||
// Attach specified handlers | ||
_.extend(Switchback, callback); | ||
// Normalize `callback` to a switchback definition object. | ||
callback = normalize.callback(callback, callbackContext); | ||
// Supply a handful of default handlers to provide better error messages. | ||
var getWildcardCaseHandler = function ( caseName, err ) { | ||
return function unknownCase ( /* ... */ ) { | ||
var args = Array.prototype.slice.call(arguments); | ||
err = (args[0] ? util.inspect(args[0])+' ' : '') + (err ? '('+(err||'')+')' : ''); | ||
// Attach specified handlers | ||
_.extend(Switchback, callback); | ||
if ( _.isObject(defaultHandlers) && _.isFunction(defaultHandlers['*']) ) { | ||
return defaultHandlers['*'](err); | ||
} | ||
else throw new Error(err); | ||
}; | ||
}; | ||
// redirect any handler defaults specified as strings | ||
if (_.isObject(defaultHandlers)) { | ||
defaultHandlers = _.mapValues(defaultHandlers, function (handler, name) { | ||
if (_.isFunction(handler)) return handler; | ||
// Closure which will resolve redirected handler | ||
return function () { | ||
var runtimeHandler = handler; | ||
var runtimeArgs = Array.prototype.slice.call(arguments); | ||
var runtimeCtx = callbackContext || this; | ||
// Supply a handful of default handlers to provide better error messages. | ||
var getWildcardCaseHandler = function(caseName, err) { | ||
return function unknownCase( /* ... */ ) { | ||
var args = Array.prototype.slice.call(arguments); | ||
err = (args[0] ? util.inspect(args[0]) + ' ' : '') + (err ? '(' + (err || '') + ')' : ''); | ||
// Track previous handler to make usage error messages more useful. | ||
var prevHandler; | ||
if (_.isObject(defaultHandlers) && _.isFunction(defaultHandlers['*'])) { | ||
return defaultHandlers['*'](err); | ||
} else throw new Error(err); | ||
}; | ||
}; | ||
// No more than 5 "redirects" allowed (prevents never-ending loop) | ||
var MAX_FORWARDS = 5; | ||
var numIterations = 0; | ||
do { | ||
prevHandler = runtimeHandler; | ||
runtimeHandler = Switchback[runtimeHandler]; | ||
// console.log('redirecting '+name+' to "'+prevHandler +'"-- got ' + runtimeHandler); | ||
numIterations++; | ||
} | ||
while ( _.isString(runtimeHandler) && numIterations <= MAX_FORWARDS); | ||
if (numIterations > MAX_FORWARDS) { | ||
throw new Error('Default handlers object ('+util.inspect(defaultHandlers)+') has a cyclic redirect.'); | ||
} | ||
// redirect any handler defaults specified as strings | ||
if (_.isObject(defaultHandlers)) { | ||
defaultHandlers = _.mapValues(defaultHandlers, function(handler, name) { | ||
if (_.isFunction(handler)) return handler; | ||
// Redirects to unknown handler | ||
if (!_.isFunction(runtimeHandler)) { | ||
runtimeHandler = getWildcardCaseHandler(runtimeHandler, '`' + name + '` case triggered, but no handler was implemented.'); | ||
} | ||
// Closure which will resolve redirected handler | ||
return function() { | ||
var runtimeHandler = handler; | ||
var runtimeArgs = Array.prototype.slice.call(arguments); | ||
var runtimeCtx = callbackContext || this; | ||
// Invoke final runtime function | ||
runtimeHandler.apply(runtimeCtx, runtimeArgs); | ||
}; | ||
}); | ||
} | ||
// Track previous handler to make usage error messages more useful. | ||
var prevHandler; | ||
_.defaults(Switchback, defaultHandlers, { | ||
success: getWildcardCaseHandler('success', '`success` case triggered, but no handler was implemented.'), | ||
error: getWildcardCaseHandler('error', '`error` case triggered, but no handler was implemented.'), | ||
invalid: getWildcardCaseHandler('invalid', '`invalid` case triggered, but no handler was implemented.') | ||
}); | ||
// No more than 5 "redirects" allowed (prevents never-ending loop) | ||
var MAX_FORWARDS = 5; | ||
var numIterations = 0; | ||
do { | ||
prevHandler = runtimeHandler; | ||
runtimeHandler = Switchback[runtimeHandler]; | ||
// console.log('redirecting '+name+' to "'+prevHandler +'"-- got ' + runtimeHandler); | ||
numIterations++; | ||
} | ||
while (_.isString(runtimeHandler) && numIterations <= MAX_FORWARDS); | ||
return Switchback; | ||
if (numIterations > MAX_FORWARDS) { | ||
throw new Error('Default handlers object (' + util.inspect(defaultHandlers) + ') has a cyclic redirect.'); | ||
} | ||
// Redirects to unknown handler | ||
if (!_.isFunction(runtimeHandler)) { | ||
runtimeHandler = getWildcardCaseHandler(runtimeHandler, '`' + name + '` case triggered, but no handler was implemented.'); | ||
} | ||
// Invoke final runtime function | ||
runtimeHandler.apply(runtimeCtx, runtimeArgs); | ||
}; | ||
}); | ||
} | ||
_.defaults(Switchback, defaultHandlers, { | ||
success: getWildcardCaseHandler('success', '`success` case triggered, but no handler was implemented.'), | ||
error: getWildcardCaseHandler('error', '`error` case triggered, but no handler was implemented.'), | ||
invalid: getWildcardCaseHandler('invalid', '`invalid` case triggered, but no handler was implemented.') | ||
}); | ||
return Switchback; | ||
}; | ||
@@ -114,11 +135,11 @@ | ||
* `isSwitchback` | ||
* | ||
* | ||
* @param {*} something | ||
* @return {Boolean} [whether `something` is a valid switchback instance] | ||
*/ | ||
switchback.isSwitchback = function (something) { | ||
return _.isObject(something) && something[constants.telltale.key] === constants.telltale.value; | ||
switchback.isSwitchback = function(something) { | ||
return _.isObject(something) && something[constants.telltale.key] === constants.telltale.value; | ||
}; | ||
module.exports = switchback; | ||
module.exports = switchback; |
{ | ||
"name": "node-switchback", | ||
"version": "0.0.4", | ||
"version": "0.1.0", | ||
"description": "Normalize callback fns to switchbacks and vice versa", | ||
@@ -5,0 +5,0 @@ "main": "lib/index.js", |
18090
12
327