thundercats
Version:
RxJS Meets isomorphic Flux
214 lines (175 loc) • 6.54 kB
JavaScript
Object.defineProperty(exports, '__esModule', {
value: true
});
exports.getActionDef = getActionDef;
exports.create = create;
exports.createMany = createMany;
exports['default'] = Actions;
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _stampit = require('stampit');
var _stampit2 = _interopRequireDefault(_stampit);
var _warning = require('warning');
var _warning2 = _interopRequireDefault(_warning);
var _debug = require('debug');
var _debug2 = _interopRequireDefault(_debug);
var _rx = require('rx');
var _waitFor = require('./waitFor');
var _waitFor2 = _interopRequireDefault(_waitFor);
var __DEV__ = process.env.NODE_ENV !== 'production';
var checkDisposed = _rx.Disposable.checkDisposed;
var assign = Object.assign;
var debug = (0, _debug2['default'])('thundercats:actions');
var currentStampSpec = ['methods', 'statics', 'props', 'refs', 'init', 'compose', 'create', 'isStamp'];
var protectedProperties = ['shouldBindMethods', 'displayName', 'constructor'].join(currentStampSpec);
function getActionDef(ctx) {
return Object.getOwnPropertyNames(ctx).filter(function (name) {
return protectedProperties.indexOf(name) === -1 && name.indexOf('_') === -1;
}).map(function (name) {
return { name: name, map: ctx[name] };
}).map(function (def) {
if (typeof def.map !== 'function') {
def.map = _rx.helpers.identity;
}
return def;
});
}
function create(shouldBind, _ref) {
var name = _ref.name;
var map = _ref.map;
var observers = [];
var actionDisposable = new _rx.CompositeDisposable();
var actionStart = new _rx.Subject();
var actionEnd = new _rx.Subject();
var maybeBound = shouldBind ? map.bind(this) : map;
function action(value) {
// throw if disposed observable is retried
checkDisposed(action);
if (action.isStopped) {
debug('%s called after being stopped', name);
return value;
}
// NOTE: if an error is thrown in the mapping function
// this will cause the stream to collapse
// and the action will no longer be observable
// nor will the observers listen as they have been stopped by
// the error
var mapDisposable = _rx.Observable.just(value).map(maybeBound).flatMap(function (value) {
if (_rx.Observable.isObservable(value)) {
return value;
}
return _rx.Observable.just(value);
})
// notify of action start
['do'](function (value) {
return actionStart.onNext(value);
})
// notify action observers
.doOnNext(function (value) {
return observers.forEach(function (observer) {
return observer.onNext(value);
});
}).doOnCompleted(function () {
return actionEnd.onNext();
}).subscribe(function () {
return debug('%s onNext', name);
}, function (err) {
// observables returned by the mapping function must use
// a catch to prevent the action from collapsing the stream
action.error = err;
action.isStopped = true;
action.hasError = true;
// notify action observers of error
observers.forEach(function (observer) {
return observer.onError(err);
});
// observers will no longer listen after pushing error
// as the stream has collapsed
// so we remove them
observers.length = 0;
});
actionDisposable.add(mapDisposable);
return value;
}
action.isDisposed = false;
action.isStopped = false;
action.displayName = name;
action.observers = observers;
assign(action, _rx.Observable.prototype);
action.hasObservers = function hasObservers() {
// in next major version this should throw if already disposed
// in order to better follow RxJS conventions
//
// checkDisposed(action);
return !!(observers.length > 0 || actionStart.observers && actionStart.observers.length > 0);
};
action.waitFor = function () {
var _arguments = arguments;
/* istanbul ignore else */
if (__DEV__) {
(0, _warning2['default'])(false, 'action.waitFor is deprecated and will be removed in ' + 'the next version of thundercats');
}
return actionStart.flatMap(function (payload) {
return _waitFor2['default'].apply(undefined, _arguments).map(function () {
return payload;
});
});
};
// NOTE: not public API. May change or be removed at any time
action.__duration = function __duration() {
return actionStart.flatMap(actionEnd).first();
};
action._subscribe = function subscribeToAction(observer) {
// in next major version this should check if action
// has been stopped or disposed and act accordingly
observers.push(observer);
return new _rx.Disposable(function () {
observers.splice(observers.indexOf(observer), 1);
});
};
var subscription = new _rx.Disposable(function () {
observers.length = 0;
action.isDisposed = true;
actionStart.dispose();
actionDisposable.dispose();
});
action.dispose = function () {
return subscription.dispose();
};
_rx.Observable.call(action);
debug('%s created', action.displayName);
return {
action: action,
subscription: subscription
};
}
function createMany(shouldBind, instance, compositeDisposable) {
return this.map(create.bind(instance, shouldBind)).reduce(function (ctx, _ref2) {
var action = _ref2.action;
var subscription = _ref2.subscription;
compositeDisposable.add(subscription);
ctx[action.displayName] = action;
return ctx;
}, {});
}
function Actions() {
var obj = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
var shouldBind = obj.shouldBindMethods;
var _obj$init = obj.init;
var init = _obj$init === undefined ? [] : _obj$init;
var _obj$props = obj.props;
var props = _obj$props === undefined ? {} : _obj$props;
var _obj$refs = obj.refs;
var refs = _obj$refs === undefined ? {} : _obj$refs;
var _obj$statics = obj.statics;
var statics = _obj$statics === undefined ? {} : _obj$statics;
return (0, _stampit2['default'])().init(function (_ref3) {
var _context;
var instance = _ref3.instance;
var actionsDisposable = new _rx.CompositeDisposable();
var actionMethods = (_context = getActionDef(obj), createMany).call(_context, shouldBind, instance, actionsDisposable);
return assign(instance, actionMethods, { dispose: function dispose() {
actionsDisposable.dispose();
} });
}).refs(refs).props(props)['static'](statics).init(init);
}
;