Permalink
Browse files

Now each next() and done() only executes its logic once, no matter ho…

…w many times it may be called within the same pre or post.
  • Loading branch information...
bnoguchi committed May 22, 2011
1 parent 88d8395 commit f71806e2770e4033f6fe33f31efcf6116c8fbf3c
Showing with 60 additions and 26 deletions.
  1. +11 −26 hooks.js
  2. +49 −0 test.js
View
@@ -1,26 +1,3 @@
-/**
- * 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 = {
/**
@@ -67,8 +44,8 @@ module.exports = {
if (currPre.length < 1)
throw new Error("Your pre must have a next argument -- e.g., function (next, ...)");
preArgs = (currPre.isAsync
- ? [_next, _asyncsDone]
- : [_next]).concat(hookArgs);
+ ? [once(_next), once(_asyncsDone)]
+ : [once(_next)]).concat(hookArgs);
return currPre.apply(self, preArgs);
} else if (!proto[name].numAsyncPres) {
return _done.apply(self, hookArgs);
@@ -93,7 +70,7 @@ module.exports = {
currPost = posts[current_]
if (currPost.length < 1)
throw new Error("Your post must have a next argument -- e.g., function (next, ...)");
- postArgs = [next_].concat(hookArgs);
+ postArgs = [once(next_)].concat(hookArgs);
return currPost.apply(self, postArgs);
}
};
@@ -164,3 +141,11 @@ module.exports = {
}
}
};
+
+function once (fn, scope) {
+ return function fnWrapper () {
+ if (fnWrapper.hookCalled) return;
+ fn.apply(scope, arguments);
+ fnWrapper.hookCalled = true;
+ };
+}
View
49 test.js
@@ -4,6 +4,7 @@ var hooks = require('./hooks')
, _ = require('underscore');
// TODO Add in test for making sure all pres get called if pre is defined directly on an instance.
+// TODO Test for calling `done` twice or `next` twice in the same function counts only once
module.exports = {
'should be able to assign multiple hooks at once': function () {
var A = function () {};
@@ -406,6 +407,54 @@ module.exports = {
a.set('foo', 'bar');
},
+ 'calling the same done multiple times should have the effect of only calling it once': function () {
+ var A = function () {
+ this.acked = false;
+ };
+ _.extend(A, hooks);
+ A.hook('ack', function () {
+ console.log("UH OH, YOU SHOULD NOT BE SEEING THIS");
+ this.acked = true;
+ });
+ A.pre('ack', function (next, done) {
+ next();
+ done();
+ done();
+ }, true);
+ A.pre('ack', function (next, done) {
+ next();
+ // Notice that done() is not invoked here
+ }, true);
+ var a = new A();
+ a.ack();
+ setTimeout( function () {
+ a.acked.should.be.false;
+ }, 1000);
+ },
+
+ 'calling the same next multiple times should have the effect of only calling it once': function () {
+ var A = function () {
+ this.acked = false;
+ };
+ _.extend(A, hooks);
+ A.hook('ack', function () {
+ console.log("UH OH, YOU SHOULD NOT BE SEEING THIS");
+ this.acked = true;
+ });
+ A.pre('ack', function (next) {
+ next();
+ next();
+ });
+ A.pre('ack', function (next) {
+ // Notice that next() is not invoked here
+ });
+ var a = new A();
+ a.ack();
+ setTimeout( function () {
+ a.acked.should.be.false;
+ }, 1000);
+ },
+
'asynchronous middleware should be able to pass an error via `done`, stopping the middleware chain': function () {
var counter = 0;
var A = function () {};

0 comments on commit f71806e

Please sign in to comment.