hooks-fixed
Version:
Adds pre and post hook functionality to your JavaScript methods.
135 lines (125 loc) • 4.1 kB
JavaScript
/**
* Hooks are useful if we want to add a method that automatically has `pre` and `post` hooks.
* For example, it would be convenient to have `pre` and `post` hooks for `save`.
* _.extend(Model, mixins.hooks);
* Model.hook('save', function () {
* console.log('saving');
* });
* Model.pre('save', function (next, done) {
* console.log('about to save');
* next();
* });
* Model.post('save', function (next, done) {
* console.log('saved');
* next();
* });
*
* var m = new Model();
* m.save();
* // about to save
* // saving
* // saved
*/
// TODO Add in pre and post skipping options
module.exports = {
/**
* Declares a new hook to which you can add pres and posts
* @param {String} name of the function
* @param {Function} the method
* @param {Function} the error handler callback
*/
hook: function (name, fn, err) {
if (arguments.length === 1 && typeof name === 'object') {
for (var k in name) { // `name` is a hash of hookName->hookFn
this.hook(k, name[k]);
}
return;
}
if (!err) err = fn;
var proto = this.prototype || this
, pres = proto._pres = proto._pres || {}
, posts = proto._posts = proto._posts || {};
pres[name] = pres[name] || [];
posts[name] = posts[name] || [];
function noop () {}
proto[name] = function () {
var self = this
, pres = this._pres[name]
, posts = this._posts[name]
, numAsyncPres = 0
, hookArgs = [].slice.call(arguments)
, preChain = pres.map( function (pre, i) {
var wrapper = function () {
if (arguments[0] instanceof Error)
return err(arguments[0]);
if (numAsyncPres) {
// arguments[1] === asyncComplete
if (arguments.length)
hookArgs = [].slice.call(arguments, 2);
pre.apply(self,
[ preChain[i+1] || allPresInvoked,
asyncComplete
].concat(hookArgs)
);
} else {
if (arguments.length)
hookArgs = [].slice.call(arguments);
pre.apply(self,
[ preChain[i+1] || allPresDone ].concat(hookArgs));
}
}; // end wrapper = function () {...
if (wrapper.isAsync = pre.isAsync)
numAsyncPres++;
return wrapper;
}); // end posts.map(...)
function allPresInvoked () {
if (arguments[0] instanceof Error)
err(arguments[0]);
}
function allPresDone () {
if (arguments[0] instanceof Error)
return err(arguments[0]);
if (arguments.length)
hookArgs = [].slice.call(arguments);
fn.apply(self, hookArgs);
var postChain = posts.map( function (post, i) {
var wrapper = function () {
if (arguments[0] instanceof Error)
return err(arguments[0]);
if (arguments.length)
hookArgs = [].slice.call(arguments);
post.apply(self,
[ postChain[i+1] || noop].concat(hookArgs));
}; // end wrapper = function () {...
return wrapper;
}); // end posts.map(...)
if (postChain.length) postChain[0]();
}
if (numAsyncPres) {
complete = numAsyncPres;
function asyncComplete () {
if (arguments[0] instanceof Error)
return err(arguments[0]);
--complete || allPresDone.call(this);
}
}
(preChain[0] || allPresDone)();
};
return this;
},
pre: function (name, fn, isAsync) {
var proto = this.prototype
, pres = proto._pres = proto._pres || {};
if (fn.isAsync = isAsync) {
this.prototype[name].numAsyncPres++;
}
(pres[name] = pres[name] || []).push(fn);
return this;
},
post: function (name, fn, isAsync) {
var proto = this.prototype
, posts = proto._posts = proto._posts || {};
(posts[name] = posts[name] || []).push(fn);
return this;
}
};