Enhance the apis and add more tests

This commit is contained in:
Raymond Feng 2015-05-26 10:15:39 -07:00
parent 12dea6e1cb
commit 621adf5435
2 changed files with 119 additions and 8 deletions

View File

@ -58,17 +58,36 @@ ObserverMixin.clearObservers = function(operation) {
}; };
/** /**
* Invoke all async observers for the given operation. * Invoke all async observers for the given operation(s).
* @param {String} operation The operation name. * @param {String|String[]} operation The operation name(s).
* @param {Object} context Operation-specific context. * @param {Object} context Operation-specific context.
* @param {function(Error=)} callback The callback to call when all observers * @param {function(Error=)} callback The callback to call when all observers
* has finished. * has finished.
*/ */
ObserverMixin.notifyObserversOf = function(operation, context, callback) { ObserverMixin.notifyObserversOf = function(operation, context, callback) {
var observers = this._observers && this._observers[operation]; var self = this;
if (!callback) callback = utils.createPromiseCallback(); if (!callback) callback = utils.createPromiseCallback();
function createNotifier(op) {
return function(ctx, done) {
if (typeof ctx === 'function' && done === undefined) {
done = ctx;
ctx = context;
}
self.notifyObserversOf(op, context, done);
};
}
if (Array.isArray(operation)) {
var tasks = [];
for (var i = 0, n = operation.length; i < n; i++) {
tasks.push(createNotifier(operation[i]));
}
return async.waterfall(tasks, callback);
}
var observers = this._observers && this._observers[operation];
this._notifyBaseObservers(operation, context, function doNotify(err) { this._notifyBaseObservers(operation, context, function doNotify(err) {
if (err) return callback(err, context); if (err) return callback(err, context);
if (!observers || !observers.length) return callback(null, context); if (!observers || !observers.length) return callback(null, context);
@ -115,21 +134,34 @@ ObserverMixin._notifyBaseObservers = function(operation, context, callback) {
*/ */
ObserverMixin.notifyObserversAround = function(operation, context, fn, callback) { ObserverMixin.notifyObserversAround = function(operation, context, fn, callback) {
var self = this; var self = this;
context = context || {};
// Add callback to the context object so that an observer can skip other
// ones by calling the callback function directly and not calling next
if (context.end === undefined) {
context.end = callback;
}
// First notify before observers
return self.notifyObserversOf('before ' + operation, context, return self.notifyObserversOf('before ' + operation, context,
function(err, context) { function(err, context) {
if (err) return callback(err, context); if (err) return callback(err);
function cbForWork(err) { function cbForWork(err) {
if (err) return callback(err, context); var args = [].slice.call(arguments, 0);
var returnedArgs = [].slice.call(arguments, 1); if (err) return callback.apply(null, args);
// Find the list of params from the callback in addition to err
var returnedArgs = args.slice(1);
// Set up the array of results
context.results = returnedArgs; context.results = returnedArgs;
// Notify after observers
self.notifyObserversOf('after ' + operation, context, self.notifyObserversOf('after ' + operation, context,
function(err, context) { function(err, context) {
if (err) return callback(err, context); if (err) return callback(err, context);
var results = returnedArgs; var results = returnedArgs;
if (context) { if (context && Array.isArray(context.results)) {
// Pickup the results from context
results = context.results; results = context.results;
} }
// Build the list of params for final callback
var args = [err].concat(results); var args = [err].concat(results);
callback.apply(null, args); callback.apply(null, args);
}); });

View File

@ -37,6 +37,18 @@ describe('async observer', function() {
}); });
}); });
it('allows multiple operations to be notified in one call', function(done) {
var notifications = [];
TestModel.observe('event1', pushAndNext(notifications, 'one'));
TestModel.observe('event2', pushAndNext(notifications, 'two'));
TestModel.notifyObserversOf(['event1', 'event2'], {}, function(err) {
if (err) return done(err);
notifications.should.eql(['one', 'two']);
done();
});
});
it('inherits observers from base model', function(done) { it('inherits observers from base model', function(done) {
var notifications = []; var notifications = [];
TestModel.observe('event', pushAndNext(notifications, 'base')); TestModel.observe('event', pushAndNext(notifications, 'base'));
@ -51,6 +63,22 @@ describe('async observer', function() {
}); });
}); });
it('allow multiple operations to be notified with base models', function(done) {
var notifications = [];
TestModel.observe('event1', pushAndNext(notifications, 'base1'));
TestModel.observe('event2', pushAndNext(notifications, 'base2'));
var Child = TestModel.extend('Child');
Child.observe('event1', pushAndNext(notifications, 'child1'));
Child.observe('event2', pushAndNext(notifications, 'child2'));
Child.notifyObserversOf(['event1', 'event2'], {}, function(err) {
if (err) return done(err);
notifications.should.eql(['base1', 'child1', 'base2', 'child2']);
done();
});
});
it('does not modify observers in the base model', function(done) { it('does not modify observers in the base model', function(done) {
var notifications = []; var notifications = [];
TestModel.observe('event', pushAndNext(notifications, 'base')); TestModel.observe('event', pushAndNext(notifications, 'base'));
@ -194,6 +222,57 @@ describe('async observer', function() {
done(); done();
}); });
}); });
it('should allow observers to skip other ones',
function(done) {
TestModel.observe('before invoke',
function(context, next) {
notifications.push('before invoke');
context.end(null, 0);
});
TestModel.observe('after invoke',
pushAndNext(notifications, 'after invoke'));
var context = {};
function work(done) {
process.nextTick(function() {
done(null, 1, 2);
});
}
TestModel.notifyObserversAround('invoke', context, work,
function(err, r1) {
r1.should.eql(0);
notifications.should.eql(['before invoke']);
done();
});
});
it('should allow observers to tweak results',
function(done) {
TestModel.observe('after invoke',
function(context, next) {
notifications.push('after invoke');
context.results = [3];
next();
});
var context = {};
function work(done) {
process.nextTick(function() {
done(null, 1, 2);
});
}
TestModel.notifyObserversAround('invoke', context, work,
function(err, r1) {
r1.should.eql(3);
notifications.should.eql(['after invoke']);
done();
});
});
}); });
it('resolves promises returned by observers', function(done) { it('resolves promises returned by observers', function(done) {