Created
August 18, 2014 19:00
-
-
Save raymondfeng/51bf3f9ed2599e4ec1ad to your computer and use it in GitHub Desktop.
This file contains hidden or 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
| 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, errorCb) { | |
| 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; | |
| } | |
| var targetClass = this; | |
| var pres, posts; | |
| if(targetClass.__pres === undefined) { | |
| Object.defineProperty(targetClass, '__pres', { | |
| enumerable: false, | |
| value: {} | |
| }); | |
| } | |
| pres = targetClass.__pres; | |
| if(targetClass.__posts === undefined) { | |
| Object.defineProperty(targetClass, '__posts', { | |
| enumerable: false, | |
| value: {} | |
| }); | |
| } | |
| posts = targetClass.__posts; | |
| pres[name] = pres[name] || []; | |
| posts[name] = posts[name] || []; | |
| targetClass[name] = function () { | |
| var self = this | |
| , hookArgs // arguments eventually passed to the hook - are mutable | |
| , lastArg = arguments[arguments.length - 1] | |
| , pres = this.__pres[name] | |
| , posts = this.__posts[name] | |
| , _total = pres.length | |
| , _current = -1 | |
| , _asyncsLeft = targetClass[name].numAsyncPres | |
| , _next = function () { | |
| if (arguments[0] instanceof Error) { | |
| return handleError(arguments[0]); | |
| } | |
| var _args = Array.prototype.slice.call(arguments) | |
| , currPre | |
| , preArgs; | |
| if (_args.length && !(arguments[0] == null && typeof lastArg === 'function')) { | |
| hookArgs = _args; | |
| } | |
| if (++_current < _total) { | |
| currPre = pres[_current] | |
| if (currPre.isAsync && currPre.length < 2) { | |
| throw new Error("Your pre must have next and done arguments -- e.g., function (next, done, ...)"); | |
| } | |
| if (currPre.length < 1) { | |
| throw new Error("Your pre must have a next argument -- e.g., function (next, ...)"); | |
| } | |
| preArgs = (currPre.isAsync | |
| ? [once(_next), once(_asyncsDone)] | |
| : [once(_next)]).concat(hookArgs); | |
| return currPre.apply(self, preArgs); | |
| } else if (!targetClass[name].numAsyncPres) { | |
| return _done.apply(self, hookArgs); | |
| } | |
| } | |
| , _done = function () { | |
| var args_ = Array.prototype.slice.call(arguments) | |
| , ret, total_, current_, next_, done_, postArgs; | |
| if (_current === _total) { | |
| next_ = function () { | |
| if (arguments[0] instanceof Error) { | |
| return handleError(arguments[0]); | |
| } | |
| var args_ = Array.prototype.slice.call(arguments, 1) | |
| , currPost | |
| , postArgs; | |
| if (args_.length){ | |
| hookArgs = args_; | |
| } | |
| if (++current_ < total_) { | |
| currPost = posts[current_] | |
| if (currPost.length < 1) { | |
| throw new Error("Your post must have a next argument -- e.g., function (next, ...)"); | |
| } | |
| postArgs = [once(next_)].concat(hookArgs); | |
| return currPost.apply(self, postArgs); | |
| } else if (typeof lastArg === 'function') { | |
| // All post handlers are done, call original callback function | |
| return lastArg.apply(self, arguments); | |
| } | |
| }; | |
| // We are assuming that if the last argument provided to the wrapped function is a function, it was expecting | |
| // a callback. We trap that callback and wait to call it until all post handlers have finished. | |
| if (typeof lastArg === 'function') { | |
| args_[args_.length - 1] = once(next_); | |
| } | |
| total_ = posts.length; | |
| current_ = -1; | |
| ret = fn.apply(self, args_); // Execute wrapped function, post handlers come afterward | |
| if (total_ && typeof lastArg !== 'function') { | |
| return next_(); | |
| } // no callback provided, execute next_() manually | |
| return ret; | |
| } | |
| }; | |
| if (_asyncsLeft) { | |
| function _asyncsDone(err) { | |
| if (err && err instanceof Error) { | |
| return handleError(err); | |
| } | |
| --_asyncsLeft || _done.apply(self, hookArgs); | |
| } | |
| } | |
| function handleError(err) { | |
| if ('function' === typeof lastArg) { | |
| return lastArg(err); | |
| } | |
| if (errorCb) { | |
| return errorCb.call(self, err); | |
| } | |
| throw err; | |
| } | |
| return _next.apply(this, arguments); | |
| }; | |
| targetClass[name].numAsyncPres = 0; | |
| return this; | |
| }, | |
| pre: function (name, isAsync, fn, errorCb) { | |
| if ('boolean' !== typeof arguments[1]) { | |
| errorCb = fn; | |
| fn = isAsync; | |
| isAsync = false; | |
| } | |
| var targetClass = this; | |
| if(targetClass.__pres === undefined) { | |
| Object.defineProperty(targetClass, '__pres', { | |
| enumerable: false, | |
| value: {} | |
| }); | |
| } | |
| var pres = targetClass.__pres; | |
| this._lazySetupHooks(targetClass, name, errorCb); | |
| if (fn.isAsync = isAsync) { | |
| targetClass[name].numAsyncPres++; | |
| } | |
| (pres[name] = pres[name] || []).push(fn); | |
| return this; | |
| }, | |
| post: function (name, isAsync, fn) { | |
| if (arguments.length === 2) { | |
| fn = isAsync; | |
| isAsync = false; | |
| } | |
| var targetClass = this; | |
| if(targetClass.__posts === undefined) { | |
| Object.defineProperty(targetClass, '__posts', { | |
| enumerable: false, | |
| value: {} | |
| }); | |
| } | |
| var posts = targetClass.__posts; | |
| this._lazySetupHooks(targetClass, name); | |
| (posts[name] = posts[name] || []).push(fn); | |
| return this; | |
| }, | |
| removePre: function (name, fnToRemove) { | |
| var targetClass = this | |
| , pres = targetClass.__pres || (targetClass.__pres || {}); | |
| if (!pres[name]) { | |
| return this; | |
| } | |
| if (arguments.length === 1) { | |
| // Remove all pre callbacks for hook `name` | |
| pres[name].length = 0; | |
| } else { | |
| pres[name] = pres[name].filter(function (currFn) { | |
| return currFn !== fnToRemove; | |
| }); | |
| } | |
| return this; | |
| }, | |
| _lazySetupHooks: function (targetClass, methodName, errorCb) { | |
| if (undefined === targetClass[methodName].numAsyncPres) { | |
| this.hook(methodName, targetClass[methodName], errorCb); | |
| } | |
| } | |
| }; | |
| function once(fn, scope) { | |
| return function fnWrapper() { | |
| if (fnWrapper.hookCalled) { | |
| return; | |
| } | |
| fnWrapper.hookCalled = true; | |
| return fn.apply(scope, arguments); | |
| }; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment