2 * Hooks are useful if we want to add a method that automatically has `pre` and `post` hooks.
3 * For example, it would be convenient to have `pre` and `post` hooks for `save`.
4 * _.extend(Model, mixins.hooks);
5 * Model.hook('save', function () {
6 * console.log('saving');
8 * Model.pre('save', function (next, done) {
9 * console.log('about to save');
12 * Model.post('save', function (next, done) {
13 * console.log('saved');
17 * var m = new Model();
24 // TODO Add in pre and post skipping options
27 * Declares a new hook to which you can add pres and posts
28 * @param {String} name of the function
29 * @param {Function} the method
30 * @param {Function} the error handler callback
32 hook: function (name, fn, err) {
33 if (arguments.length === 1 && typeof name === 'object') {
34 for (var k in name) { // `name` is a hash of hookName->hookFn
35 this.hook(k, name[k]);
42 var proto = this.prototype || this
43 , pres = proto._pres = proto._pres || {}
44 , posts = proto._posts = proto._posts || {};
45 pres[name] = pres[name] || [];
46 posts[name] = posts[name] || [];
50 proto[name] = function () {
52 , pres = this._pres[name]
53 , posts = this._posts[name]
55 , hookArgs = [].slice.call(arguments)
56 , preChain = pres.map( function (pre, i) {
57 var wrapper = function () {
58 if (arguments[0] instanceof Error)
59 return err(arguments[0]);
61 // arguments[1] === asyncComplete
63 hookArgs = [].slice.call(arguments, 2);
65 [ preChain[i+1] || allPresInvoked,
71 hookArgs = [].slice.call(arguments);
73 [ preChain[i+1] || allPresDone ].concat(hookArgs));
75 }; // end wrapper = function () {...
76 if (wrapper.isAsync = pre.isAsync)
79 }); // end posts.map(...)
80 function allPresInvoked () {
81 if (arguments[0] instanceof Error)
85 function allPresDone () {
86 if (arguments[0] instanceof Error)
87 return err(arguments[0]);
89 hookArgs = [].slice.call(arguments);
90 fn.apply(self, hookArgs);
91 var postChain = posts.map( function (post, i) {
92 var wrapper = function () {
93 if (arguments[0] instanceof Error)
94 return err(arguments[0]);
96 hookArgs = [].slice.call(arguments);
98 [ postChain[i+1] || noop].concat(hookArgs));
99 }; // end wrapper = function () {...
101 }); // end posts.map(...)
102 if (postChain.length) postChain[0]();
106 complete = numAsyncPres;
107 function asyncComplete () {
108 if (arguments[0] instanceof Error)
109 return err(arguments[0]);
110 --complete || allPresDone.call(this);
113 (preChain[0] || allPresDone)();
119 pre: function (name, fn, isAsync) {
120 var proto = this.prototype
121 , pres = proto._pres = proto._pres || {};
122 if (fn.isAsync = isAsync) {
123 this.prototype[name].numAsyncPres++;
125 (pres[name] = pres[name] || []).push(fn);
128 post: function (name, fn, isAsync) {
129 var proto = this.prototype
130 , posts = proto._posts = proto._posts || {};
131 (posts[name] = posts[name] || []).push(fn);