UNPKG

asyncplify

Version:

FRP (functional reactive programming) library for Javascript

1,506 lines (1,499 loc) 66.6 kB
(function () { 'use strict'; var debug = typeof require === 'undefined' ? function () { return noop; } : require('debug'); function Asyncplify(func, arg, source) { this._arg = arg; this._func = func; this._src = source; } Asyncplify.prototype._subscribe = function (observer) { new this._func(this._arg, observer, this._src, this); }; Asyncplify.states = { RUNNING: 0, PAUSED: 1, CLOSED: 2 }; Asyncplify.prototype.catch = function (options) { return new Asyncplify(Catch, options, this); }; function Catch(options, sink, source) { this.i = 0; this.sink = sink; this.sink.source = this; this.source = null; this.sources = null; if (typeof options === 'function') this.mapper = options; else this.sources = Array.isArray(options) ? options : []; source._subscribe(this); } Catch.prototype = { emit: function (value) { this.sink.emit(value); }, end: function (err) { this.source = null; if (err) { var source = this.mapper(err); if (source && this.sink) return source._subscribe(this); } this.mapper = null; this.sink.end(err); }, mapper: function () { return this.i < this.sources.length && this.sources[this.i++]; }, setState: function (state) { if (this.source) this.source.setState(state); } }; Asyncplify.combineLatest = function (options) { return new Asyncplify(CombineLatest, options); }; function CombineLatest(options, sink) { var items = options && options.items || options; this.closableCount = items.length; this.mapper = options && options.mapper || null; this.missingValuesCount = options && options.emitUndefined ? 0 : items.length; this.sink = sink; this.sink.source = this; this.subscriptions = []; this.values = []; var i; for (i = 0; i < items.length; i++) this.values.push(undefined); for (i = 0; i < items.length; i++) this.subscriptions.push(new CombineLatestItem(items[i], this, i)); if (!items.length) this.sink.end(null); } CombineLatest.prototype.setState = function (state) { for (var i = 0; i < this.subscriptions.length; i++) this.subscriptions[i].setState(state); this.subscriptions.length = 0; }; function CombineLatestItem(source, parent, index) { this.hasValue = false; this.index = index; this.parent = parent; this.source = null; source._subscribe(this); } CombineLatestItem.prototype = { emit: function (v) { this.parent.values[this.index] = v; if (!this.hasValue) { this.hasValue = true; this.parent.missingValuesCount--; } if (this.parent.missingValuesCount <= 0) { var array = this.parent.values.slice(); this.parent.sink.emit(this.parent.mapper ? this.parent.mapper.apply(null, array) : array); } }, end: function (err) { this.source = null; this.parent.closableCount--; if (err || !this.parent.closableCount) { this.parent.sink.end(err); if (err) this.parent.setState(Asyncplify.states.CLOSED); } }, setState: function (state) { if (this.source) this.source.setState(state); } }; Asyncplify.concat = function (sources) { return new Asyncplify(Concat, sources); }; Asyncplify.prototype.concat = function (sources) { return new Asyncplify(Concat, [this].concat(sources)); }; function Concat(sources, sink) { this.isSubscribing = false; this.sink = sink; this.sink.source = this; this.source = null; this.sources = (sources || []).concat(); this.state = Asyncplify.states.RUNNING; if (this.sources.length) this.subscribe(); else this.sink.end(null); } Concat.prototype = { emit: function (value) { this.sink.emit(value); }, end: function (err) { this.source = null; if (err || !this.sources.length) { this.sources.length = 0; this.sink.end(err); } else { this.subscribe(); } }, setState: function (state) { if (this.state !== state && this.state !== Asyncplify.states.CLOSED) { this.state = state; if (this.source) this.source.setState(state); this.subscribe(); } }, subscribe: function () { if (!this.isSubscribing) { while (this.sources.length && !this.source && this.state === Asyncplify.states.RUNNING) { this.isSubscribing = true; this.sources.shift()._subscribe(this); this.isSubscribing = false; } } } }; Asyncplify.prototype.concatMap = function (mapper) { return new Asyncplify(ConcatMap, mapper, this); }; function ConcatMap(mapper, sink, source) { this.isSubscribing = false; this.items = []; this.mapItem = null; this.mapper = mapper || identify; this.sink = sink; this.sink.source = this; this.source = null; this.state = Asyncplify.states.RUNNING; source._subscribe(this); } ConcatMap.prototype = { childEnd: function (err) { this.mapItem = null; if (err || !this.items.length && !this.source) { this.items.length = 0; if (this.source) this.source.setState(Asyncplify.states.CLOSED); this.source = null; this.sink.end(err); } else if (!this.isSubscribing) { this.subscribe(); } }, emit: function (value) { this.items.push(this.mapper(value)); this.subscribe(); }, end: function (err) { this.source = null; if (err || !this.mapItem && !this.items.length) { if (this.mapItem) this.mapItem.setState(Asyncplify.states.CLOSED); this.mapItem = null; this.items.length = 0; this.sink.end(err); } }, setState: function (state) { if (this.state !== state && this.state !== Asyncplify.states.CLOSED) { this.state = state; if (this.mapItem) this.mapItem.setState(state); if (this.source) this.source.setState(state); this.subscribe(); } }, subscribe: function () { while (!this.mapItem && this.items.length && this.state === Asyncplify.states.RUNNING) { this.isSubscribing = true; this.mapItem = new FlatMapItem(this); this.items.shift()._subscribe(this.mapItem); this.isSubscribing = false; } } }; Asyncplify.prototype.count = function (cond) { return new Asyncplify(Count, cond, this); }; function Count(cond, sink, source) { this.cond = cond || condTrue; this.sink = sink; this.sink.source = this; this.source = null; this.value = 0; source._subscribe(this); } Count.prototype = { emit: function (value) { if (this.cond(value)) this.value++; }, end: function (err) { this.source = null; if (!err) this.sink.emit(this.value); this.sink.end(err); }, setState: function (state) { if (this.source) this.source.setState(state); } }; Asyncplify.prototype.debounce = function (options) { return new Asyncplify(Debounce, options, this); }; function Debounce(options, sink, source) { this.delay = options && options.delay || typeof options === 'number' && options || 0; this.itemPending = false; this.scheduler = (options && options.scheduler || schedulers.timeout)(); this.sink = sink; this.sink.source = this; this.source = null; this.value = undefined; source._subscribe(this); } Debounce.prototype = { action: function () { var v = this.value; this.itemPending = false; this.value = undefined; this.sink.emit(v); if (!this.source) { this.scheduler.setState(Asyncplify.states.CLOSED); this.sink.end(null); } }, emit: function (value) { this.itemPending = true; this.value = value; this.scheduler.reset(); this.scheduler.schedule(this); }, end: function (err) { this.source = null; if (err || !this.itemPending) { this.scheduler.setState(Asyncplify.states.CLOSED); this.value = undefined; this.sink.end(err); } }, setState: function (state) { this.scheduler.setState(state); if (this.source) this.source.setState(state); } }; Asyncplify.prototype.defaultIfEmpty = function (value) { return new Asyncplify(DefaultIfEmpty, value, this); }; function DefaultIfEmpty(value, sink, source) { this.hasValue = false; this.sink = sink; this.sink.source = this; this.source = null; this.value = value; source._subscribe(this); } DefaultIfEmpty.prototype = { emit: function (value) { this.hasValue = true; this.sink.emit(value); }, end: function (err) { this.source = null; if (!this.hasValue && !err) this.sink.emit(this.value); this.sink.end(err); }, setState: function (state) { if (this.source) this.source.setState(state); } }; Asyncplify.empty = function () { return new Asyncplify(Empty); }; function Empty(_, sink) { sink.source = this; sink.end(null); } Empty.prototype.setState = noop; Asyncplify.prototype.expand = function (selector) { return new Asyncplify(Expand, selector, this); }; //TODO Add isSubscribing to expand //TODO Better implement pause function Expand(mapper, sink, source) { this.error = null; this.items = []; this.mapper = mapper || identity; this.selectPending = false; this.sink = sink; this.sink.source = this; this.state = Asyncplify.states.RUNNING; this.source = null; this.subscribables = []; this.value = undefined; source._subscribe(this); } Expand.prototype = { emit: function (value) { if (this.state !== Asyncplify.states.CLOSED) { this.sink.emit(value); var source = this.mapper(value); if (source) { var item = new ExpandItem(this); this.items.push(item); source._subscribe(item); } } }, end: function (err) { this.source = null; if (err) { for (var i = 0; i < this.items.length; i++) this.items[i].setState(Asyncplify.states.CLOSED); this.items.length = 0; } if (!this.items.length) { this.mapper = noop; this.sink.end(err); } }, setState: function (state) { this.state = state; if (this.source) this.source.setState(state); for (var i = 0; i < this.items.length; i++) this.items[i].setState(state); } }; function ExpandItem(parent) { this.parent = parent; this.source = null; } ExpandItem.prototype = { emit: function (v) { this.parent.emit(v); }, end: function (err) { this.source = null; removeItem(this.parent.items, this); if (err) { for (var i = 0; i < this.parent.items.length; i++) this.parent.items[i].close(); this.parent.items.length = 0; } if (!this.parent.items.length && !this.source) { this.parent.mapper = noop; this.parent.sink.end(err); } }, setState: function (state) { if (this.source) this.source.setState(state); } }; if (typeof module !== 'undefined') { module.exports = Asyncplify; } else if (typeof window !== 'undefined') { window.Asyncplify = window.asyncplify = Asyncplify; } Asyncplify.prototype.filter = function (cond) { if (typeof cond === 'function') return new Asyncplify(Filter, cond, this); if (cond === false) return new Asyncplify(Filter, condFalse, this); return this; }; function Filter(cond, sink, source) { this.cond = cond; this.sink = sink; this.sink.source = this; this.source = null; source._subscribe(this); } Filter.prototype = { emit: function (value) { if (this.cond(value)) this.sink.emit(value); }, end: function (err) { this.source = null; this.sink.end(err); }, setState: function (state) { if (this.source) this.source.setState(state); } }; Asyncplify.prototype.finally = function (action) { return action ? new Asyncplify(Finally, action, this) : this; }; function Finally(action, sink, source) { this.action = action; this.sink = sink; this.sink.source = this; this.source = null; this.registerProcessEnd(true); source._subscribe(this); } Finally.prototype = { emit: function (value) { this.sink.emit(value); }, end: function (err) { this.source = null; this.registerProcessEnd(false); this.action(); this.action = noop; this.sink.end(err); }, registerProcessEnd: function (register) { if (typeof process === 'object') { var n = register ? 'on' : 'removeListener'; process[n]('SIGINT', this.action); process[n]('SIGQUIT', this.action); process[n]('SIGTERM', this.action); } }, setState: function (state) { if (this.source) { this.source.setState(state); if (state === Asyncplify.states.CLOSED) { this.source = null; this.registerProcessEnd(false); this.action(); } } } }; Asyncplify.prototype.flatMap = function (options) { return new Asyncplify(FlatMap, options, this); }; var flatMapDebug = debug('asyncplify:flatMap'); function FlatMap(options, sink, source) { this.isPaused = false; this.items = []; this.mapper = options.mapper || options || identity; this.maxConcurrency = Math.max(options.maxConcurrency || 0, 0); this.sink = sink; this.sink.source = this; this.state = Asyncplify.states.RUNNING; this.source = null; flatMapDebug('subscribe'); source._subscribe(this); } FlatMap.prototype = { childEnd: function (err, item) { var count = this.items.length; removeItem(this.items, item); if (err) { this.setState(Asyncplify.states.CLOSED); this.sink.end(err); } else if (!this.items.length && !this.source) { flatMapDebug('end'); this.sink.end(null); } else if (this.source && this.maxConcurrency && count === this.maxConcurrency && this.isPaused) { this.isPaused = false; if (this.state === Asyncplify.states.RUNNING) { flatMapDebug('resuming source'); this.source.setState(Asyncplify.states.RUNNING); } } }, emit: function (v) { flatMapDebug('receive %j', v); var item = this.mapper(v); if (item) { var flatMapItem = new FlatMapItem(this, flatMapDebug); this.items.push(flatMapItem); if (this.maxConcurrency && this.items.length >= this.maxConcurrency && !this.isPaused) { this.isPaused = true; flatMapDebug('pausing source because of max concurrency.'); this.source.setState(Asyncplify.states.PAUSED); } flatMapDebug('subscribe to item.'); item._subscribe(flatMapItem); } }, end: function (err) { flatMapDebug('source completed'); this.source = null; if (err) this.setState(Asyncplify.states.CLOSED); if (err || !this.items.length) { err ? flatMapDebug('error', err) : flatMapDebug('end'); this.sink.end(err); } }, setState: function (state) { if (this.state !== state && this.state !== Asyncplify.states.CLOSED) { this.state = state; if (this.source && (!this.isPaused || state === Asyncplify.states.CLOSED)) this.source.setState(state); for (var i = 0; i < this.items.length && this.state === state; i++) this.items[i].setState(state); } } }; function FlatMapItem(parent, debug) { this.debug = debug || noop; this.parent = parent; this.source = null; } FlatMapItem.prototype = { emit: function (v) { this.debug('flatMapItem emit %j', v); this.parent.sink.emit(v); }, end: function (err) { err ? this.debug('flatMapItem error', err) : this.debug('flatMapItem end'); this.parent.childEnd(err, this); }, setState: function (state) { if (this.source) this.source.setState(state); } }; Asyncplify.prototype.flatMapLatest = function (options) { return new Asyncplify(FlatMapLatest, options, this); }; function FlatMapLatest(options, sink, source) { this.mapper = options || identity; this.sink = sink; this.sink.source = this; this.source = null; this.subscription = null; source._subscribe(this); } FlatMapLatest.prototype = { childEnd: function (err, item) { this.subscription = null; if (err && this.source) { this.source.setState(Asyncplify.states.CLOSED); this.source = null; this.mapper = noop; } if (err || !this.source) this.sink.end(err); }, emit: function (v) { var item = this.mapper(v); if (item) { if (this.subscription) this.subscription.setState(Asyncplify.states.CLOSED); this.subscription = new FlatMapItem(this); item._subscribe(this.subscription); } }, end: function (err) { this.mapper = noop; this.source = null; if (err && this.subscription) { this.subscription.setState(Asyncplify.states.CLOSED); this.subscription = null; } if (err || !this.subscription) this.sink.end(err); }, setState: function (state) { if (this.source) this.source.setState(state); if (this.subscription) this.subscription.setState(state); } }; Asyncplify.fromArray = function (array) { return new Asyncplify(FromArray, array); }; function FromArray(array, sink) { this.array = array; this.i = 0; this.isProcessing = false; this.sink = sink; this.sink.source = this; this.state = Asyncplify.states.RUNNING; this.emitItems(); } FromArray.prototype = { emitItems: function () { this.isProcessing = true; while (this.i < this.array.length && this.state === Asyncplify.states.RUNNING) this.sink.emit(this.array[this.i++]); if (this.state === Asyncplify.states.RUNNING) { this.array = []; this.state = Asyncplify.states.CLOSED; this.sink.end(null); } this.isProcessing = false; }, setState: function (state) { if (this.state !== Asyncplify.states.CLOSED && this.state !== state) { this.state = state; if (state === Asyncplify.states.RUNNING && !this.isProcessing) this.emitItems(); } } }; Asyncplify.fromNode = function (func) { var args = []; for (var i = 1; i < arguments.length; i++) { args.push(arguments[i]); } return new Asyncplify(FromNode, [ func, args ]); }; function FromNode(options, sink) { this.sink = sink; this.sink.source = this; var self = this; function callback(err, value) { if (!err) self.sink.emit(value); self.sink.end(err); } try { options[0].apply(null, options[1].concat([callback])); } catch (ex) { this.sink.end(ex); } } FromNode.prototype.setState = function (state) { if (state === Asyncplify.states.CLOSED) this.sink = NoopSink.instance; }; Asyncplify.fromPromise = function (promise, cb) { return new Asyncplify(FromPromise, promise); }; function FromPromise(promise, sink) { this.sink = sink; this.sink.source = this; var self = this; function resolve(v) { self.sink.emit(v); self.sink.end(null); } function rejected(err) { self.sink.end(err); } promise.then(resolve, rejected); } FromPromise.prototype.setState = function (state) { if (state === Asyncplify.states.CLOSED) this.sink = NoopSink.instance; }; Asyncplify.fromRx = function (obs) { return new Asyncplify(FromRx, obs); }; function FromRx(obs, sink) { sink.source = this; function next(value) { sink.emit(value); } function error(err) { sink.end(err); } function completed() { sink.end(null); } this.subscription = obs.subscribe(next, error, completed); } FromRx.prototype.setState = function (state) { if (state === Asyncplify.states.CLOSED) this.subscription.dispose(); }; Asyncplify.prototype.groupBy = function (options) { return new Asyncplify(GroupBy, options, this); }; function GroupBy(options, sink, source) { this.elementSelector = options && options.elementSelector || identity; this.keySelector = typeof options === 'function' ? options : options && options.keySelector || identity; this.sink = sink; this.sink.source = this; this.source = null; this.store = options && options.store || {}; source._subscribe(this); } GroupBy.prototype = { emit: function (v) { var key = this.keySelector(v); var group = this.store[key]; if (!group) { group = this.store[key] = Asyncplify.subject(); group.key = key; this.sink.emit(group); } group.emit(this.elementSelector(v)); }, end: function (err) { for (var k in this.store) { this.store[k].end(null); } this.sink.end(err); }, setState: function (state) { if (this.source) this.source.setState(state); } }; Asyncplify.prototype.ignoreElements = function () { return new Asyncplify(IgnoreElements, null, this); }; function IgnoreElements(_, sink, source) { this.sink = sink; this.sink.source = this; this.source = null; source._subscribe(this); } IgnoreElements.prototype = { emit: noop, end: function (err) { this.source = null; this.sink.end(err); }, setState: function (state) { if (this.source) this.source.setState(state); } }; Asyncplify.infinite = function () { return new Asyncplify(Infinite); }; function Infinite(_, sink) { this.sink = sink; this.sink.source = this; while (this.sink) this.sink.emit(); } Infinite.prototype.setState = function (state) { if (state === Asyncplify.states.CLOSED) this.sink = null; }; Asyncplify.interval = function (options) { return new Asyncplify(Interval, options); }; function Interval(options, sink) { this.i = 0; this.delay = options && options.delay || typeof options === 'number' && options || 0; this.scheduler = (options && options.scheduler || schedulers.timeout)(); this.sink = sink; this.sink.source = this; this.scheduler.schedule(this); } Interval.prototype = { action: function () { this.sink.emit(this.i++); this.scheduler.schedule(this); }, setState: function (state) { this.scheduler.setState(state); } }; Asyncplify.prototype.last = function (cond) { return new Asyncplify(Last, cond, this); }; function Last(cond, sink, source) { this.cond = cond || condTrue; this.hasItem = false; this.item = null; this.sink = sink; this.sink.source = this; this.source = null; source._subscribe(this); } Last.prototype = { emit: function (value) { if (this.cond(value)) { this.item = value; this.hasItem = true; } }, end: function (err) { this.source = null; if (!err && this.hasItem) this.sink.emit(this.item); this.sink.end(err); }, setState: function (state) { if (this.source) this.source.setState(state); } }; Asyncplify.prototype.map = function (mapper) { return mapper ? new Asyncplify(Map, mapper, this) : this; }; function Map(mapper, sink, source) { this.mapper = mapper; this.sink = sink; this.sink.source = this; this.source = null; source._subscribe(this); } Map.prototype = { emit: function (value) { this.sink.emit(this.mapper(value)); }, end: function (err) { this.mapper = noop; this.source = null; this.sink.end(err); }, setState: function (state) { if (this.source) this.source.setState(state); } }; Asyncplify.merge = function (options) { return new Asyncplify(Merge, options); }; function Merge(options, sink) { this.concurrency = 0; this.index = 0; this.items = options.items || options || []; this.maxConcurrency = options.maxConcurrency || 0; this.sink = sink; this.sink.source = this; this.subscriptions = []; while (this.index < this.items.length && (this.maxConcurrency < 1 || this.concurrency < this.maxConcurrency)) new MergeItem(this.items[this.index++], this); if (!this.items.length) this.sink.end(null); } Merge.prototype.setState = function (state) { for (var i = 0; i < this.subscriptions.length; i++) this.subscriptions[i].setState(state); this.subscriptions.length = 0; }; function MergeItem(item, parent) { this.parent = parent; this.source = null; parent.concurrency++; parent.subscriptions.push(this); item._subscribe(this); } MergeItem.prototype = { emit: function (v) { this.parent.sink.emit(v); }, end: function (err) { if (this.source) { this.source = null; this.parent.concurrency--; removeItem(this.parent.subscriptions, this); if (err || this.parent.index >= this.parent.items.length) { var sink = this.parent.sink; this.parent.setState(Asyncplify.states.CLOSED); sink.end(err); } else if (this.parent.maxConcurrency < 1 || this.parent.concurrency < this.parent.maxConcurrency) { new MergeItem(this.parent.items[this.parent.index++], this.parent); } } }, setState: function (state) { if (this.source) this.source.setState(state); } }; Asyncplify.never = function () { return new Asyncplify(Never); }; function Never(_, sink) { sink.source = this; } Never.prototype.setState = noop; Asyncplify.prototype.observeOn = function (options) { return new Asyncplify(ObserveOn, options, this); }; function ObserveOn(options, sink, source) { this.scheduler = (typeof options === 'function' ? options : options && options.scheduler || schedulers.immediate)(); this.sink = sink; this.sink.source = this; this.source = null; source._subscribe(this); } ObserveOn.prototype = { emit: function (v) { this.scheduler.schedule(new ObserveOnItem(v, true, this)); }, end: function (err) { this.scheduler.schedule(new ObserveOnItem(err, false, this)); }, setState: function (state) { if (this.source) this.source.setState(state); if (this.scheduler) this.scheduler.setState(state); } }; function ObserveOnItem(value, isEmit, parent) { this.isEmit = isEmit; this.parent = parent; this.value = value; } ObserveOnItem.prototype = { action: function () { this.isEmit ? this.parent.sink.emit(this.value) : this.parent.sink.end(this.value); }, error: function (err) { this.parent.sink.end(err); this.parent.setState(Asyncplify.states.CLOSED); }, delay: 0 }; Asyncplify.prototype.pipe = function (func) { return func(this); }; Asyncplify.range = function (options) { return new Asyncplify(RangeOp, options); }; function RangeOp(options, sink) { this.end = typeof options === 'number' ? options : options && options.end || 0; this.i = options && options.start || 0; this.sink = sink; this.sink.source = this; this.state = Asyncplify.states.RUNNING; this.step = options && options.step || 1; this.emitValues(); } RangeOp.prototype = { emitValues: function () { while (this.i < this.end && this.state === Asyncplify.states.RUNNING) { var j = this.i; this.i += this.step; this.sink.emit(j); } if (this.state === Asyncplify.states.RUNNING) { this.state = Asyncplify.states.CLOSED; this.sink.end(null); } }, setState: function (state) { if (this.state !== state && this.state !== Asyncplify.states.CLOSED) { this.state = state; if (state === Asyncplify.states.RUNNING) this.emitValues(); } } }; /*Flow.prototype.recurse = function (options) { var produce = options.produce; var feedback = options.feedback; var self = this; return new Flow(function (subscriber) { var subscriptions = []; var paused = subscriber.paused; function subscribeFeedback(item) { var s = item.subscription({ emit: function (v) { var t = produce(v); t && t.subscription && subscribeProduce(t); }, end: function (err) { endSubscribe(subscriptions, s, subscriber, err); }, paused: true }); subscriptions.push(s); !paused && s.resume(); } function subscribeProduce(item) { var s = item.subscription({ emit: function (v) { if (v != undefined) { subscriber.emit(v); var t = feedback(v); t && t.subscription && subscribeFeedback(t); } }, end: function (err) { endSubscribe(subscriptions, s, subscriber, err); }, paused: true }); subscriptions.push(s); !paused && s.resume(); } !paused && subscribeFeedback(self); return { close: function () { closeSubscriptions(subscriptions); }, pause: function () { paused = true; pauseSubscriptions(subscriptions); }, resume: function () { paused = false; resumeSubscriptions(subscriptions); } } }); }; */ Asyncplify.prototype.scan = function (options) { return new Asyncplify(Scan, options, this); }; function scanIdentity(acc, v) { return (acc || 0) + (v || 0); } function Scan(options, sink, source) { this.acc = options && options.initial || 0; this.mapper = typeof options === 'function' ? options : options && options.mapper || scanIdentity; this.sink = sink; this.sink.source = this; this.source = null; source._subscribe(this); } Scan.prototype = { emit: function (value) { this.acc = this.mapper(this.acc, value); this.sink.emit(this.acc); }, end: function (err) { this.mapper = noop; this.source = null; this.sink.end(err); }, setState: function (state) { if (this.source) this.source.setState(state); } }; Asyncplify.prototype.share = function (scheduler) { var r = new Asyncplify(Share, null, this); r.emit = shareEmit; r.end = shareEnd; r.source = null; r._refs = []; r._scheduler = null; r._schedulerFactory = scheduler || schedulers.immediate; return r; }; function shareEmit(value) { for (var i = 0; i < this._refs.length; i++) this._refs[i].emit(value); } function shareEnd(err) { var array = this._refs; this.source = null; this._refs = []; for (var i = 0; i < array.length; i++) array[i].end(err); } function Share(_, sink, source, parent) { this.parent = parent; this.sink = sink; this.sink.source = this; this.state = Asyncplify.states.RUNNING; parent._refs.push(this); if (parent._refs.length === 1) { this.parent._scheduler = this.parent._schedulerFactory(); parent._scheduler.schedule({ action: function () { source._subscribe(parent); }, error: function (err) { parent.end(err); } }); } } Share.prototype = { emit: function (value) { this.sink.emit(value); }, end: function (err) { this.state = Asyncplify.states.CLOSED; this.parent = null; this.sink.end(err); }, setState: function (state) { if (this.state !== state && this.state !== Asyncplify.states.CLOSED) { this.state = state; switch (state) { case Asyncplify.states.RUNNING: for (var i = 0; i < this.parent._refs.length; i++) if (this.parent._refs[i].state !== Asyncplify.states.RUNNING) return; break; case Asyncplify.states.CLOSED: removeItem(this.parent._refs, this); if (this.parent._refs.length) return; break; } if (this.parent._scheduler) this.parent._scheduler.setState(state); if (this.parent.source) this.parent.source.setState(state); } } }; Asyncplify.prototype.skip = function (count) { return typeof count !== 'number' || count <= 0 ? this : new Asyncplify(Skip, count, this); }; function Skip(count, sink, source) { this.count = count; this.sink = sink; this.sink.source = this; this.source = null; source._subscribe(this); } Skip.prototype = { emit: function (value) { if (this.count > 0) this.count--; else this.sink.emit(value); }, end: function (err) { this.source = null; this.sink.end(err); }, setState: function (state) { if (this.source) this.source.setState(state); } }; Asyncplify.prototype.skipLast = function (count) { return new Asyncplify(SkipLast, typeof count === 'number' ? count : 1, this); }; function SkipLast(count, sink, source) { this.count = count; this.items = []; this.sink = sink; this.sink.source = this; this.source = null; source._subscribe(this); } SkipLast.prototype = { emit: function (value) { this.items.push(value); this.items.length > this.count && this.sink.emit(this.items.splice(0, 1)[0]); }, end: function (err) { this.source = null; this.items.length = 0; this.sink.end(err); }, setState: function (state) { if (this.source) this.source.setState(state); } }; Asyncplify.prototype.skipUntil = function (trigger) { return new Asyncplify(SkipUntil, trigger, this); }; function SkipUntil(trigger, sink, source) { this.can = false; this.sink = sink; this.sink.source = this; this.source = null; this.trigger = null; new Trigger(trigger, this); source._subscribe(this); } SkipUntil.prototype = { emit: function (value) { if (this.can) this.sink.emit(value); }, end: function (err) { if (this.trigger) this.trigger.setState(Asyncplify.states.CLOSED); this.trigger = this.source = null; this.sink.end(err); }, setState: function (state) { if (this.trigger) this.trigger.setState(state); if (this.source) this.source.setState(state); }, triggerEmit: function () { this.trigger.setState(Asyncplify.states.CLOSED); this.trigger = null; this.can = true; } }; Asyncplify.prototype.skipWhile = function (cond) { return new Asyncplify(SkipWhile, cond, this); }; function SkipWhile(cond, sink, source) { this.can = false; this.cond = cond; this.sink = sink; this.sink.source = this; this.source = null; source._subscribe(this); } SkipWhile.prototype = { emit: function (value) { if (this.can || !this.cond(value)) { this.can = true; this.sink.emit(value); } }, end: function (err) { this.cond = condTrue; this.source = null; this.sink.end(err); }, setState: function (state) { if (this.source) this.source.setState(state); } }; Asyncplify.subject = function () { var r = new Asyncplify(Subject); r.subjects = []; r.emit = subjectEmit; r.end = subjectEnd; r._src = r; return r; }; function subjectEmit(value) { for (var i = 0; i < this.subjects.length; i++) this.subjects[i].emit(value); } function subjectEnd(err) { var subjects = this.subjects; this.subjects = []; for (var i = 0; i < subjects.length; i++) subjects[i].end(err); } function Subject(_, sink, parent) { this.parent = parent; this.sink = sink; this.sink.source = this; parent.subjects.push(this); } Subject.prototype = { emit: function (value) { this.sink.emit(value); }, end: function (err) { this.parent = null; this.sink.end(err); }, setState: function (state) { if (state === Asyncplify.states.CLOSED && this.parent) { removeItem(this.parent.subjects, this); this.parent = null; } } }; Asyncplify.prototype.subscribe = function (options) { return new Subscribe(options, this); }; function Subscribe(options, source) { if (options && options.emit) this.emit = options.emit; else if (typeof options === 'function') this.emit = options; if (options && options.end) this.end = options.end; this.source = null; source._subscribe(this); } Subscribe.prototype = { close: function () { this.source.setState(Asyncplify.states.CLOSED); }, pause: function () { this.source.setState(Asyncplify.states.PAUSED); }, resume: function () { this.source.setState(Asyncplify.states.RUNNING); }, emit: noop, end: function (err) { if (err) throw err; } }; Asyncplify.prototype.subscribeOn = function (options) { return new Asyncplify(SubscribeOn, options, this); }; function SubscribeOn(options, sink, source) { this.origin = source; this.sink = sink; this.sink.source = this; this.scheduler = (typeof options === 'function' ? options : options && options.scheduler || schedulers.immediate)(); this.source = null; this.scheduler.schedule(this); } SubscribeOn.prototype = { action: function () { this.scheduler.setState(Asyncplify.states.CLOSED); this.scheduler = null; this.origin._subscribe(this); this.origin = null; }, emit: function (value) { this.sink.emit(value); }, end: function (err) { this.source = null; this.sink.end(err); }, error: function (err) { if (this.scheduler) this.scheduler.setState(Asyncplify.states.CLOSED); if (this.source) this.source.setState(Asyncplify.states.CLOSED); this.scheduler = this.source = this.origin = null; this.sink.end(err); }, setState: function (state) { if (this.scheduler) this.scheduler.setState(state); if (this.source) this.source.setState(state); } }; Asyncplify.prototype.sum = function (mapper, source, cb) { return new Asyncplify(Sum, mapper || identity, this); }; function Sum(mapper, sink, source) { this.hasValue = false; this.mapper = mapper; this.sink = sink; this.sink.source = this; this.source = null; this.value = 0; source._subscribe(this); } Sum.prototype = { emit: function (value) { this.value += this.mapper(value) || 0; this.hasValue = true; }, end: function (err) { this.source = null; if (!err && this.hasValue && this.sink) this.sink.emit(this.value); this.sink.end(err); }, setState: function (state) { if (this.source) this.source.setState(state); } }; Asyncplify.prototype.take = function (count) { return new Asyncplify(count > 0 ? Take : Empty, count, this); }; function Take(count, sink, source) { this.count = count; this.sink = sink; this.sink.source = this; this.source = null; source._subscribe(this); } Take.prototype = { emit: function (value) { if (this.count--) { this.sink.emit(value); if (!this.count) { this.source.setState(Asyncplify.states.CLOSED); this.source = null; this.sink.end(null); } } }, end: function (err) { this.source = null; this.sink.end(err); }, setState: function (state) { if (this.source) this.source.setState(state); } }; Asyncplify.prototype.takeUntil = function (trigger) { return new Asyncplify(TakeUntil, trigger, this); }; function TakeUntil(trigger, sink, source) { this.sink = sink; this.sink.source = this; this.source = null; this.trigger = null; new Trigger(trigger, this); if (this.trigger) source._subscribe(this); } TakeUntil.prototype = { emit: function (value) { this.sink.emit(value); }, end: function (err) { if (this.trigger) this.trigger.setState(Asyncplify.states.CLOSED); this.source = this.trigger = null; this.sink.end(err); }, setState: function (state) { if (this.source) this.source.setState(state); if (this.trigger) this.trigger.setState(state); }, triggerEmit: function () { if (this.source) this.source.setState(Asyncplify.states.CLOSED); this.trigger.setState(Asyncplify.states.CLOSED); this.source = this.trigger = null; this.sink.end(null); } }; Asyncplify.prototype.takeWhile = function (cond) { return new Asyncplify(TakeWhile, cond, this); }; function TakeWhile(cond, sink, source) { this.cond = cond; this.sink = sink; this.sink.source = this; this.source = null; source._subscribe(this); } TakeWhile.prototype = { emit: function (value) { if (this.cond(value)) { this.sink.emit(value); } else { this.source.setState(Asyncplify.states.CLOSED); this.source = null; this.cond = noop; this.sink.end(null); } }, end: function (err) { this.source = null; this.cond = noop; this.sink.end(err); }, setState: function (state) { if (this.source) this.source.setState(state); } }; Asyncplify.prototype.tap = function (options) { return new Asyncplify(Tap, options, this); }; function Tap(options, sink, source) { this._emit = options && options.emit || typeof options === 'function' && options || noop; this._end = options && options.end || noop; this._setState = options && options.setState || noop; this.sink = sink; this.