Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

basic guard functionality #96

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
85 changes: 85 additions & 0 deletions guard.js
@@ -0,0 +1,85 @@
/** @license MIT License (c) copyright B Cavalier & J Hann */

/**
* guard.js
*
* Wait until the guard condition is true before proceeding
* with deferred execution.
*
* @author sakari@rocketpack.fi
*/

(function(define) {
define(['./when', './function'], function(when, func) {

function Guard(condition) {
if (condition.enter) {
this._condition = condition;
} else {
var users = 0;
this._condition = {
enter: function() {
if(users < condition) {
users++;
return true;
}
return false;
},
exit: function() {
users--;
}
};
}
this._pending = [];
}
Guard.prototype._trigger = function() {
var self = this;
if(this._pending.length === 0 || !this._condition.enter()) {
return;
}
var next = this._pending.shift();
func.apply(next.fn, next.args)
.then(function() {
next.dfd.resolve.apply(next.dfd, Array.prototype.slice.apply(arguments));
}, function() {
next.dfd.reject.apply(next.dfd, Array.prototype.slice.apply(arguments));
})
.always(function() {
self._condition.exit();
self._trigger();
});
};

Guard.prototype.do = function(fn) {
var self = this;
return function() {
var dfd = when.defer();
self._pending.push({dfd: dfd, fn: fn, args: Array.prototype.slice.apply(arguments)});
self._trigger();
return dfd;
};
};

/**
* Wait until condition before proceeding
* @param condition
* @return {Guard} guard object with `do` method
*
* The `do` method takes a function which should return a deferred.
* The argument functions are called when the guard condition is true.
*/
return function guard(condition) {
return new Guard(condition);
};

});
})(typeof define == 'function' && define.amd
? define
: function (deps, factory) { typeof exports == 'object'
? (module.exports = factory(require('./when'), require('./function')))
: (this.when_guard = factory(this.when, this.when_function));
}
// Boilerplate for AMD, Node, and browser global
);


97 changes: 97 additions & 0 deletions test/guard.js
@@ -0,0 +1,97 @@
(function(buster, guard, when) {

var assert, refute, fail, resolved, rejected;

assert = buster.assert;
refute = buster.refute;
fail = buster.assertions.fail;

resolved = when.resolve;
rejected = when.reject;

buster.testCase('when/guard', {
'should be usable for throttling when.map': function(done) {
var dfds = [0, 1, 2, 3].map(function() {
return when.defer();
});
when.map([0, 1, 2, 3], guard(2).do(function(v) {
return dfds[v].resolve(v * 10);
})).then(function(v) {
assert.equals(v, [0, 10, 20, 30]);
done();
});
},
'should guard the function with `enter` and `exit` calls': function(done) {
var got = [];
var g = guard({
enter: function() { got.push('enter'); return true; },
exit: function() { got.push('exit'); }
});
g.do(function() {
return when.defer().resolve();
})().then(function() {
assert.equals(got, ['enter', 'exit']);
done();
});
},
'should convert normal functions to return deferreds': function(done) {
guard(1).do(function() { return 1; })()
.then(function(v) {
assert.equals(v, 1);
done();
});
},
'should allow deferred arguments': function(done) {
var g = guard(1).do(function(v) { return v; });
g(when.defer().resolve(1))
.then(function(v) {
assert.equals(v, 1);
done();
});
},
'should call exit even when the function throws': function(done) {
var got = [];
var condition = {
exit: function() { got.push('exit'); },
enter: function() { got.push('enter'); return true;}
};
guard(condition)
.do(function() { throw new Error(); })()
.then(null, function() {
assert.equals(got, ['enter', 'exit']);
done();
});
},
'should be sharable between functions': function(done) {
var g = guard(1);
var first;
var d = when.defer();
g.do(function() {
first = true;
return d;
})();
g.do(function() {
assert.equals(first, true);
done();
})();
d.resolve();
},
'should limit concurrent users using a shorthand': function(done) {
var calls = [];
var first = when.defer();
var dfds = [first.then(function() { assert.equals(calls, [0]); }),
when.defer().resolve()];
var g = guard(1).do(function(v) { calls.push(v); return dfds[v]; });
g(0);
g(1).then(function() {
assert.equals(calls, [0, 1]);
done();
});
first.resolve();
}
});
})(
this.buster || require('buster'),
this.when_guard || require('../guard'),
this.when || require('../when')
);