fluo
Version:
A prototype-based fork of the Reflux data flow library similar to Facebook Flux
201 lines (163 loc) • 5.34 kB
JavaScript
var _ = require('./utils');
/**
* @constructor
*/
var Publisher = function () {
/**
* @protected
*/
this.emitter = new _.EventEmitter();
this.children = [];
this.eventType = 'event';
};
/**
* Hook used by the publisher that is invoked before emitting
* and before `shouldEmit`. The arguments are the ones that the action
* is invoked with. If this function returns something other than
* undefined, that will be passed on as arguments for shouldEmit and
* emission.
*/
Publisher.prototype.preEmit = function () {};
/**
* Hook used by the publisher after `preEmit` to determine if the
* event should be emitted with given arguments. This may be overridden
* in your application, default implementation always returns true.
*
* @returns {Boolean} true if event should be emitted
*/
Publisher.prototype.shouldEmit = function () {
return true;
};
/**
* Subscribes the given callback for action triggered
*
* @param {Function} callback The callback to register as event handler
* @param {Mixed} [optional] bindContext The context to bind the callback with
* @returns {Function} Callback that unsubscribes the registered event handler
*/
Publisher.prototype.listen = function (callback, bindContext) {
var self = this;
bindContext = bindContext || this;
var eventHandler = function (args) {
var result = callback.apply(bindContext, args);
if (_.isPromise(result)) {
var canHandlePromise = Publisher.prototype.canHandlePromise.call(self);
if (!canHandlePromise) {
console.warn('Unhandled promise for ' + self.eventType);
return;
}
self.resolve(result);
}
};
this.emitter.addListener(this.eventType, eventHandler);
return function () {
self.emitter.removeListener(self.eventType, eventHandler);
};
};
Publisher.prototype.listenOnce = function (callback, bindContext) {
bindContext = bindContext || this;
var unsubscribe = this.listen(function () {
var args = Array.prototype.slice.call(arguments);
unsubscribe();
return callback.apply(bindContext, args);
});
return unsubscribe;
};
/**
* Attach handlers to promise that trigger the completed and failed
* child publishers, if available.
*
* @param {Object} promise The result to use or a promise to which to listen.
*/
Publisher.prototype.resolve = function (promise) {
var canHandlePromise = Publisher.prototype.canHandlePromise.call(this);
if (!canHandlePromise) {
throw new Error('Not an async publisher');
}
if (!_.isPromise(promise)) {
this.completed.trigger(promise);
return;
}
var self = this;
promise.then(function (response) {
return self.completed.trigger(response);
});
// IE compatibility - catch is a reserved word - without bracket notation source compilation will fail under IE
promise["catch"](function (error) {
return self.failed.trigger(error);
});
};
Publisher.prototype.reject = function (result) {
if (_.isPromise(result)) {
console.warn('Use #resolve() for promises.');
return;
}
this.failed.trigger(result);
};
Publisher.prototype.then = function (onSuccess, onFailure) {
var canHandlePromise = Publisher.prototype.canHandlePromise.call(this);
if (!canHandlePromise) {
throw new Error('Not an async publisher');
}
if (onSuccess) {
this.completed.listenOnce(onSuccess);
}
if (onFailure) {
this.failed.listenOnce(onFailure);
}
};
Publisher.prototype['catch'] = function (onFailure) {
this.then(null, onFailure);
};
Publisher.prototype.canHandlePromise = function () {
return _.isAction(this.completed) && _.isAction(this.failed);
};
/**
* Publishes an event using `this.emitter` (if `shouldEmit` agrees)
*/
Publisher.prototype.triggerSync = function () {
var args = arguments;
var preResult = this.preEmit.apply(this, args);
if (typeof preResult !== 'undefined') {
args = _.isArguments(preResult) ? preResult : [].concat(preResult);
}
if (this.shouldEmit.apply(this, args)) {
this.emitter.emit(this.eventType, args);
}
};
/**
* Tries to publish the event on the next tick
*/
Publisher.prototype.trigger = function () {
var args = arguments;
var self = this;
_.nextTick(function () {
self.triggerSync.apply(self, args);
});
};
/**
* Returns a Promise for the triggered action
*/
Publisher.prototype.triggerPromise = function () {
var canHandlePromise = Publisher.prototype.canHandlePromise.call(this);
if (!canHandlePromise) {
throw new Error('Publisher must have "completed" and "failed" child publishers');
}
var self = this;
var args = arguments;
var promise = _.createPromise(function (resolve, reject) {
var removeSuccess = self.completed.listen(function (args) {
removeSuccess();
removeFailed();
resolve(args);
});
var removeFailed = self.failed.listen(function (args) {
removeSuccess();
removeFailed();
reject(args);
});
self.trigger.apply(self, args);
});
return promise;
};
module.exports = Publisher;