Skip to content

Commit 8dae264

Browse files
committed
Modified old spec implementation to the new spec. Moved alternative implementation from last commit into its own file hooks.alt.js
1 parent 48ea020 commit 8dae264

File tree

2 files changed

+196
-110
lines changed

2 files changed

+196
-110
lines changed

Diff for: hooks.alt.js

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/**
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');
7+
* });
8+
* Model.pre('save', function (next, done) {
9+
* console.log('about to save');
10+
* next();
11+
* });
12+
* Model.post('save', function (next, done) {
13+
* console.log('saved');
14+
* next();
15+
* });
16+
*
17+
* var m = new Model();
18+
* m.save();
19+
* // about to save
20+
* // saving
21+
* // saved
22+
*/
23+
24+
// TODO Add in pre and post skipping options
25+
module.exports = {
26+
/**
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
31+
*/
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]);
36+
}
37+
return;
38+
}
39+
40+
if (!err) err = fn;
41+
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] || [];
47+
48+
function noop () {}
49+
50+
proto[name] = function () {
51+
var self = this
52+
, pres = this._pres[name]
53+
, posts = this._posts[name]
54+
, numAsyncPres = 0
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]);
60+
if (numAsyncPres) {
61+
// arguments[1] === asyncComplete
62+
if (arguments.length)
63+
hookArgs = [].slice.call(arguments, 2);
64+
pre.apply(self,
65+
[ preChain[i+1] || allPresInvoked,
66+
asyncComplete
67+
].concat(hookArgs)
68+
);
69+
} else {
70+
if (arguments.length)
71+
hookArgs = [].slice.call(arguments);
72+
pre.apply(self,
73+
[ preChain[i+1] || allPresDone ].concat(hookArgs));
74+
}
75+
}; // end wrapper = function () {...
76+
if (wrapper.isAsync = pre.isAsync)
77+
numAsyncPres++;
78+
return wrapper;
79+
}); // end posts.map(...)
80+
function allPresInvoked () {
81+
if (arguments[0] instanceof Error)
82+
err(arguments[0]);
83+
}
84+
85+
function allPresDone () {
86+
if (arguments[0] instanceof Error)
87+
return err(arguments[0]);
88+
if (arguments.length)
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]);
95+
if (arguments.length)
96+
hookArgs = [].slice.call(arguments);
97+
post.apply(self,
98+
[ postChain[i+1] || noop].concat(hookArgs));
99+
}; // end wrapper = function () {...
100+
return wrapper;
101+
}); // end posts.map(...)
102+
if (postChain.length) postChain[0]();
103+
}
104+
105+
if (numAsyncPres) {
106+
complete = numAsyncPres;
107+
function asyncComplete () {
108+
if (arguments[0] instanceof Error)
109+
return err(arguments[0]);
110+
--complete || allPresDone.call(this);
111+
}
112+
}
113+
(preChain[0] || allPresDone)();
114+
};
115+
116+
return this;
117+
},
118+
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++;
124+
}
125+
(pres[name] = pres[name] || []).push(fn);
126+
return this;
127+
},
128+
post: function (name, fn, isAsync) {
129+
var proto = this.prototype
130+
, posts = proto._posts = proto._posts || {};
131+
(posts[name] = posts[name] || []).push(fn);
132+
return this;
133+
}
134+
};

Diff for: hooks.js

+62-110
Original file line numberDiff line numberDiff line change
@@ -29,144 +29,96 @@ module.exports = {
2929
* @param {Function} the method
3030
* @param {Function} the error handler callback
3131
*/
32-
hook: function (name, fn, err) {
32+
hook: function (name, fn, errorCb) {
3333
if (arguments.length === 1 && typeof name === 'object') {
3434
for (var k in name) { // `name` is a hash of hookName->hookFn
3535
this.hook(k, name[k]);
3636
}
3737
return;
3838
}
3939

40-
if (!err) err = fn;
40+
if (!errorCb) errorCb = fn;
4141

4242
var proto = this.prototype || this
4343
, pres = proto._pres = proto._pres || {}
4444
, posts = proto._posts = proto._posts || {};
4545
pres[name] = pres[name] || [];
4646
posts[name] = posts[name] || [];
4747

48-
function noop () {}
49-
50-
// NEW CODE
5148
proto[name] = function () {
5249
var self = this
50+
, hookArgs // arguments eventually passed to the hook - are mutable
5351
, pres = this._pres[name]
5452
, posts = this._posts[name]
55-
, numAsyncPres = 0
56-
, hookArgs = [].slice.call(arguments)
57-
, preChain = pres.map( function (pre, i) {
58-
var wrapper = function () {
59-
if (arguments[0] instanceof Error)
60-
return err(arguments[0]);
61-
if (numAsyncPres) {
62-
// arguments[1] === asyncComplete
63-
if (arguments.length)
64-
hookArgs = [].slice.call(arguments, 2);
65-
pre.apply(self,
66-
[ preChain[i+1] || allPresInvoked,
67-
asyncComplete
68-
].concat(hookArgs)
69-
);
70-
} else {
71-
if (arguments.length)
72-
hookArgs = [].slice.call(arguments);
73-
pre.apply(self,
74-
[ preChain[i+1] || allPresDone ].concat(hookArgs));
75-
}
76-
}; // end wrapper = function () {...
77-
if (wrapper.isAsync = pre.isAsync)
78-
numAsyncPres++;
79-
return wrapper;
80-
}); // end posts.map(...)
81-
function allPresInvoked () {
82-
if (arguments[0] instanceof Error)
83-
err(arguments[0]);
84-
}
85-
86-
function allPresDone () {
87-
if (arguments[0] instanceof Error)
88-
return err(arguments[0]);
89-
if (arguments.length)
90-
hookArgs = [].slice.call(arguments);
91-
fn.apply(self, hookArgs);
92-
var postChain = posts.map( function (post, i) {
93-
var wrapper = function () {
53+
, _total = pres.length
54+
, _current = -1
55+
, _asyncsLeft = proto[name].numAsyncPres
56+
, _next = function () {
9457
if (arguments[0] instanceof Error)
95-
return err(arguments[0]);
96-
if (arguments.length)
97-
hookArgs = [].slice.call(arguments);
98-
post.apply(self,
99-
[ postChain[i+1] || noop].concat(hookArgs));
100-
}; // end wrapper = function () {...
101-
return wrapper;
102-
}); // end posts.map(...)
103-
if (postChain.length) postChain[0]();
104-
}
105-
106-
if (numAsyncPres) {
107-
complete = numAsyncPres;
108-
function asyncComplete () {
109-
if (arguments[0] instanceof Error)
110-
return err(arguments[0]);
111-
--complete || allPresDone.call(this);
58+
return errorCb(arguments[0]);
59+
var _args = Array.prototype.slice.call(arguments)
60+
, currPre
61+
, preArgs;
62+
if (_args.length) hookArgs = _args;
63+
if (++_current < _total) {
64+
currPre = pres[_current]
65+
if (currPre.isAsync && currPre.length < 2)
66+
throw new Error("Your pre must have next and done arguments -- e.g., function (next, done, ...)");
67+
if (currPre.length < 1)
68+
throw new Error("Your pre must have a next argument -- e.g., function (next, ...)");
69+
preArgs = (currPre.isAsync
70+
? [_next, _asyncsDone]
71+
: [_next]).concat(hookArgs);
72+
return currPre.apply(self, preArgs);
73+
} else if (!proto[name].numAsyncPres) {
74+
return _done.apply(self, hookArgs);
75+
}
76+
}
77+
, _done = function () {
78+
var args_ = Array.prototype.slice.call(arguments)
79+
, ret, total_, current_, next_, done_, postArgs;
80+
if (_current === _total) {
81+
ret = fn.apply(self, args_);
82+
total_ = posts.length;
83+
current_ = -1;
84+
next_ = function () {
85+
if (arguments[0] instanceof Error)
86+
return errorCb(arguments[0]);
87+
var args_ = Array.prototype.slice.call(arguments, 1)
88+
, currPost
89+
, postArgs;
90+
if (args_.length) hookArgs = args_;
91+
if (++current_ < total_) {
92+
currPost = posts[current_]
93+
if (currPost.length < 1)
94+
throw new Error("Your post must have a next argument -- e.g., function (next, ...)");
95+
postArgs = [next_].concat(hookArgs);
96+
return currPost.apply(self, postArgs);
97+
}
98+
};
99+
if (total_) return next_();
100+
return ret;
101+
}
102+
};
103+
if (_asyncsLeft) {
104+
function _asyncsDone () {
105+
--_asyncsLeft || _done.apply(self, hookArgs);
112106
}
113107
}
114-
(preChain[0] || allPresDone)();
108+
return _next.apply(this, arguments);
115109
};
110+
111+
proto[name].numAsyncPres = 0;
116112

117-
// proto[name] = function () {
118-
// var self = this
119-
// , hookArgs // arguments eventually passed to the hook - are mutable
120-
// , pres = this._pres[name]
121-
// , posts = this._posts[name]
122-
// , _total = pres.length
123-
// , _current = -1
124-
// , _next = function () {
125-
// var _args = Array.prototype.slice.call(arguments)
126-
// , currPre
127-
// , preArgs;
128-
// if (_args.length) hookArgs = _args;
129-
// if (++_current < _total) {
130-
// currPre = pres[_current]
131-
// if (currPre.length < 2) throw new Error("Your pre must have next and done arguments -- e.g., function (next, done, ...)");
132-
// preArgs = [_next, _done].concat( (currPre.length === 3) ? [hookArgs] : hookArgs);
133-
// return currPre.apply(self, preArgs);
134-
// } else return _done.apply(self, [null].concat(hookArgs));
135-
// }
136-
// , _done = function () {
137-
// var err = arguments[0]
138-
// , args_ = Array.prototype.slice.call(arguments, 1)
139-
// , ret, total_, current_, next_, done_, postArgs;
140-
// if (_current === _total) {
141-
// ret = fn.apply(self, args_);
142-
// total_ = posts.length;
143-
// current_ = -1;
144-
// next_ = function () {
145-
// var args_ = Array.prototype.slice.call(arguments)
146-
// , currPost
147-
// , postArgs;
148-
// if (args_.length) hookArgs = args_;
149-
// if (++current_ < total_) {
150-
// currPost = posts[current_]
151-
// if (currPost.length < 2) throw new Error("Your post must have next and done arguments -- e.g., function (next, done, ...)");
152-
// postArgs = [next_, done_].concat( (currPost.length ===3) ? [hookArgs] : hookArgs);
153-
// return posts[current_].apply(self, postArgs);
154-
// }
155-
// else return done_();
156-
// };
157-
// done_ = function () { return ret; };
158-
// if (total_) return next_();
159-
// return ret;
160-
// }
161-
// };
162-
// return _next.apply(this, arguments);
163-
// };
164113
return this;
165114
},
115+
166116
pre: function (name, fn, isAsync) {
167117
var proto = this.prototype
168118
, pres = proto._pres = proto._pres || {};
169-
fn.isAsync = isAsync;
119+
if (fn.isAsync = isAsync) {
120+
this.prototype[name].numAsyncPres++;
121+
}
170122
(pres[name] = pres[name] || []).push(fn);
171123
return this;
172124
},

0 commit comments

Comments
 (0)