asyncplify
Version:
FRP (functional reactive programming) library for Javascript
1,506 lines (1,499 loc) • 66.6 kB
JavaScript
(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.