kefir
Version:
Reactive Programming library for JavaScript inspired by Bacon.js and RxJS with focus on high performance and low memory usage
2,529 lines (2,015 loc) • 60.9 kB
JavaScript
/*! Kefir.js v1.3.0
* https://github.com/pozadi/kefir
*/
;(function(global){
"use strict";
var Kefir = {};
function and() {
for (var i = 0; i < arguments.length; i++) {
if (!arguments[i]) {
return arguments[i];
}
}
return arguments[i - 1];
}
function or() {
for (var i = 0; i < arguments.length; i++) {
if (arguments[i]) {
return arguments[i];
}
}
return arguments[i - 1];
}
function not(x) {
return !x;
}
function concat(a, b) {
var result, length, i, j;
if (a.length === 0) {
return b;
}
if (b.length === 0) {
return a;
}
j = 0;
result = new Array(a.length + b.length);
length = a.length;
for (i = 0; i < length; i++, j++) {
result[j] = a[i];
}
length = b.length;
for (i = 0; i < length; i++, j++) {
result[j] = b[i];
}
return result;
}
function circleShift(arr, distance) {
var length = arr.length
, result = new Array(length)
, i;
for (i = 0; i < length; i++) {
result[(i + distance) % length] = arr[i];
}
return result;
}
function find(arr, value) {
var length = arr.length
, i;
for (i = 0; i < length; i++) {
if (arr[i] === value) {
return i;
}
}
return -1;
}
function findByPred(arr, pred) {
var length = arr.length
, i;
for (i = 0; i < length; i++) {
if (pred(arr[i])) {
return i;
}
}
return -1;
}
function cloneArray(input) {
var length = input.length
, result = new Array(length)
, i;
for (i = 0; i < length; i++) {
result[i] = input[i];
}
return result;
}
function remove(input, index) {
var length = input.length
, result, i, j;
if (index >= 0 && index < length) {
if (length === 1) {
return [];
} else {
result = new Array(length - 1);
for (i = 0, j = 0; i < length; i++) {
if (i !== index) {
result[j] = input[i];
j++;
}
}
return result;
}
} else {
return input;
}
}
function removeByPred(input, pred) {
return remove(input, findByPred(input, pred));
}
function map(input, fn) {
var length = input.length
, result = new Array(length)
, i;
for (i = 0; i < length; i++) {
result[i] = fn(input[i]);
}
return result;
}
function forEach(arr, fn) {
var length = arr.length
, i;
for (i = 0; i < length; i++) {
fn(arr[i]);
}
}
function fillArray(arr, value) {
var length = arr.length
, i;
for (i = 0; i < length; i++) {
arr[i] = value;
}
}
function contains(arr, value) {
return find(arr, value) !== -1;
}
function rest(arr, start, onEmpty) {
if (arr.length > start) {
return Array.prototype.slice.call(arr, start);
}
return onEmpty;
}
function slide(cur, next, max) {
var length = Math.min(max, cur.length + 1),
offset = cur.length - length + 1,
result = new Array(length),
i;
for (i = offset; i < length; i++) {
result[i - offset] = cur[i];
}
result[length - 1] = next;
return result;
}
function isEqualArrays(a, b) {
var length, i;
if (a == null && b == null) {
return true;
}
if (a == null || b == null) {
return false;
}
if (a.length !== b.length) {
return false;
}
for (i = 0, length = a.length; i < length; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
function spread(fn, length) {
switch(length) {
case 0: return function(a) {return fn();};
case 1: return function(a) {return fn(a[0]);};
case 2: return function(a) {return fn(a[0], a[1]);};
case 3: return function(a) {return fn(a[0], a[1], a[2]);};
case 4: return function(a) {return fn(a[0], a[1], a[2], a[3]);};
default: return function(a) {return fn.apply(null, a);};
}
}
function apply(fn, c, a) {
var aLength = a ? a.length : 0;
if (c == null) {
switch (aLength) {
case 0: return fn();
case 1: return fn(a[0]);
case 2: return fn(a[0], a[1]);
case 3: return fn(a[0], a[1], a[2]);
case 4: return fn(a[0], a[1], a[2], a[3]);
default: return fn.apply(null, a);
}
} else {
switch (aLength) {
case 0: return fn.call(c);
default: return fn.apply(c, a);
}
}
}
function get(map, key, notFound) {
if (map && key in map) {
return map[key];
} else {
return notFound;
}
}
function own(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
function createObj(proto) {
var F = function() {};
F.prototype = proto;
return new F();
}
function extend(target /*, mixin1, mixin2...*/) {
var length = arguments.length
, i, prop;
for (i = 1; i < length; i++) {
for (prop in arguments[i]) {
target[prop] = arguments[i][prop];
}
}
return target;
}
function inherit(Child, Parent /*, mixin1, mixin2...*/) {
var length = arguments.length
, i;
Child.prototype = createObj(Parent.prototype);
Child.prototype.constructor = Child;
for (i = 2; i < length; i++) {
extend(Child.prototype, arguments[i]);
}
return Child;
}
var NOTHING = ['<nothing>'];
var END = 'end';
var VALUE = 'value';
var ERROR = 'error';
var ANY = 'any';
function noop() {}
function id(x) {
return x;
}
function id2(_, x) {
return x;
}
function strictEqual(a, b) {
return a === b;
}
function defaultDiff(a, b) {
return [a, b];
}
var now = Date.now ?
function() {
return Date.now();
} :
function() {
return new Date().getTime();
};
var log = ((typeof console !== undefined) && isFn(console.log)) ?
function(m) {
console.log(m);
} : noop;
Kefir.DEPRECATION_WARNINGS = true;
function deprecated(name, alt, fn) {
var message = 'Method `' + name + '` is deprecated, and to be removed in v3.0.0.';
if (alt) {
message += '\nUse `' + alt + '` instead.';
}
message += '\nTo disable all warnings like this set `Kefir.DEPRECATION_WARNINGS = false`.';
return function() {
if (Kefir.DEPRECATION_WARNINGS) {
log(message);
}
return fn.apply(this, arguments);
};
}
function isFn(fn) {
return typeof fn === 'function';
}
function isUndefined(x) {
return typeof x === 'undefined';
}
function isArrayLike(xs) {
return isArray(xs) || isArguments(xs);
}
var isArray = Array.isArray || function(xs) {
return Object.prototype.toString.call(xs) === '[object Array]';
};
var isArguments = function(xs) {
return Object.prototype.toString.call(xs) === '[object Arguments]';
};
// For IE
if (!isArguments(arguments)) {
isArguments = function(obj) {
return !!(obj && own(obj, 'callee'));
};
}
function withInterval(name, mixin) {
function AnonymousStream(wait, args) {
Stream.call(this);
this._wait = wait;
this._intervalId = null;
var $ = this;
this._$onTick = function() {
$._onTick();
};
this._init(args);
}
inherit(AnonymousStream, Stream, {
_name: name,
_init: function(args) {},
_free: function() {},
_onTick: function() {},
_onActivation: function() {
this._intervalId = setInterval(this._$onTick, this._wait);
},
_onDeactivation: function() {
if (this._intervalId !== null) {
clearInterval(this._intervalId);
this._intervalId = null;
}
},
_clear: function() {
Stream.prototype._clear.call(this);
this._$onTick = null;
this._free();
}
}, mixin);
Kefir[name] = function(wait) {
return new AnonymousStream(wait, rest(arguments, 1, []));
};
}
function withOneSource(name, mixin, options) {
options = extend({
streamMethod: function(StreamClass, PropertyClass) {
return function() {
return new StreamClass(this, arguments);
};
},
propertyMethod: function(StreamClass, PropertyClass) {
return function() {
return new PropertyClass(this, arguments);
};
}
}, options || {});
mixin = extend({
_init: function(args) {},
_free: function() {},
_handleValue: function(x, isCurrent) {
this._send(VALUE, x, isCurrent);
},
_handleError: function(x, isCurrent) {
this._send(ERROR, x, isCurrent);
},
_handleEnd: function(__, isCurrent) {
this._send(END, null, isCurrent);
},
_handleAny: function(event) {
switch (event.type) {
case VALUE: this._handleValue(event.value, event.current); break;
case ERROR: this._handleError(event.value, event.current); break;
case END: this._handleEnd(event.value, event.current); break;
}
},
_onActivation: function() {
this._source.onAny(this._$handleAny);
},
_onDeactivation: function() {
this._source.offAny(this._$handleAny);
}
}, mixin || {});
function buildClass(BaseClass) {
function AnonymousObservable(source, args) {
BaseClass.call(this);
this._source = source;
this._name = source._name + '.' + name;
this._init(args);
var $ = this;
this._$handleAny = function(event) {
$._handleAny(event);
};
}
inherit(AnonymousObservable, BaseClass, {
_clear: function() {
BaseClass.prototype._clear.call(this);
this._source = null;
this._$handleAny = null;
this._free();
}
}, mixin);
return AnonymousObservable;
}
var AnonymousStream = buildClass(Stream);
var AnonymousProperty = buildClass(Property);
if (options.streamMethod) {
Stream.prototype[name] = options.streamMethod(AnonymousStream, AnonymousProperty);
}
if (options.propertyMethod) {
Property.prototype[name] = options.propertyMethod(AnonymousStream, AnonymousProperty);
}
}
function withTwoSources(name, mixin /*, options*/) {
mixin = extend({
_init: function(args) {},
_free: function() {},
_handlePrimaryValue: function(x, isCurrent) {
this._send(VALUE, x, isCurrent);
},
_handlePrimaryError: function(x, isCurrent) {
this._send(ERROR, x, isCurrent);
},
_handlePrimaryEnd: function(__, isCurrent) {
this._send(END, null, isCurrent);
},
_handleSecondaryValue: function(x, isCurrent) {
this._lastSecondary = x;
},
_handleSecondaryError: function(x, isCurrent) {
this._send(ERROR, x, isCurrent);
},
_handleSecondaryEnd: function(__, isCurrent) {},
_handlePrimaryAny: function(event) {
switch (event.type) {
case VALUE:
this._handlePrimaryValue(event.value, event.current);
break;
case ERROR:
this._handlePrimaryError(event.value, event.current);
break;
case END:
this._handlePrimaryEnd(event.value, event.current);
break;
}
},
_handleSecondaryAny: function(event) {
switch (event.type) {
case VALUE:
this._handleSecondaryValue(event.value, event.current);
break;
case ERROR:
this._handleSecondaryError(event.value, event.current);
break;
case END:
this._handleSecondaryEnd(event.value, event.current);
this._removeSecondary();
break;
}
},
_removeSecondary: function() {
if (this._secondary !== null) {
this._secondary.offAny(this._$handleSecondaryAny);
this._$handleSecondaryAny = null;
this._secondary = null;
}
},
_onActivation: function() {
if (this._secondary !== null) {
this._secondary.onAny(this._$handleSecondaryAny);
}
if (this._alive) {
this._primary.onAny(this._$handlePrimaryAny);
}
},
_onDeactivation: function() {
if (this._secondary !== null) {
this._secondary.offAny(this._$handleSecondaryAny);
}
this._primary.offAny(this._$handlePrimaryAny);
}
}, mixin || {});
function buildClass(BaseClass) {
function AnonymousObservable(primary, secondary, args) {
BaseClass.call(this);
this._primary = primary;
this._secondary = secondary;
this._name = primary._name + '.' + name;
this._lastSecondary = NOTHING;
var $ = this;
this._$handleSecondaryAny = function(event) {
$._handleSecondaryAny(event);
};
this._$handlePrimaryAny = function(event) {
$._handlePrimaryAny(event);
};
this._init(args);
}
inherit(AnonymousObservable, BaseClass, {
_clear: function() {
BaseClass.prototype._clear.call(this);
this._primary = null;
this._secondary = null;
this._lastSecondary = null;
this._$handleSecondaryAny = null;
this._$handlePrimaryAny = null;
this._free();
}
}, mixin);
return AnonymousObservable;
}
var AnonymousStream = buildClass(Stream);
var AnonymousProperty = buildClass(Property);
Stream.prototype[name] = function(secondary) {
return new AnonymousStream(this, secondary, rest(arguments, 1, []));
};
Property.prototype[name] = function(secondary) {
return new AnonymousProperty(this, secondary, rest(arguments, 1, []));
};
}
// Dispatcher
function callSubscriber(sType, sFn, event) {
if (sType === ANY) {
sFn(event);
} else if (sType === event.type) {
if (sType === VALUE || sType === ERROR) {
sFn(event.value);
} else {
sFn();
}
}
}
function Dispatcher() {
this._items = [];
}
extend(Dispatcher.prototype, {
add: function(type, fn) {
this._items = concat(this._items, [{
type: type,
fn: fn
}]);
return this._items.length;
},
remove: function(type, fn) {
this._items = removeByPred(this._items, function(fnData) {
return fnData.type === type && fnData.fn === fn;
});
return this._items.length;
},
dispatch: function(event) {
var items = this._items;
for (var i = 0; i < items.length; i++) {
callSubscriber(items[i].type, items[i].fn, event);
}
}
});
// Events
function Event(type, value, current) {
return {type: type, value: value, current: !!current};
}
var CURRENT_END = Event(END, undefined, true);
// Observable
function Observable() {
this._dispatcher = new Dispatcher();
this._active = false;
this._alive = true;
}
Kefir.Observable = Observable;
extend(Observable.prototype, {
_name: 'observable',
_onActivation: function() {},
_onDeactivation: function() {},
_setActive: function(active) {
if (this._active !== active) {
this._active = active;
if (active) {
this._onActivation();
} else {
this._onDeactivation();
}
}
},
_clear: function() {
this._setActive(false);
this._alive = false;
this._dispatcher = null;
},
_send: function(type, x, isCurrent) {
if (this._alive) {
this._dispatcher.dispatch(Event(type, x, isCurrent));
if (type === END) {
this._clear();
}
}
},
_on: function(type, fn) {
if (this._alive) {
this._dispatcher.add(type, fn);
this._setActive(true);
} else {
callSubscriber(type, fn, CURRENT_END);
}
return this;
},
_off: function(type, fn) {
if (this._alive) {
var count = this._dispatcher.remove(type, fn);
if (count === 0) {
this._setActive(false);
}
}
return this;
},
onValue: function(fn) {
return this._on(VALUE, fn);
},
onError: function(fn) {
return this._on(ERROR, fn);
},
onEnd: function(fn) {
return this._on(END, fn);
},
onAny: function(fn) {
return this._on(ANY, fn);
},
offValue: function(fn) {
return this._off(VALUE, fn);
},
offError: function(fn) {
return this._off(ERROR, fn);
},
offEnd: function(fn) {
return this._off(END, fn);
},
offAny: function(fn) {
return this._off(ANY, fn);
}
});
// extend() can't handle `toString` in IE8
Observable.prototype.toString = function() {
return '[' + this._name + ']';
};
// Stream
function Stream() {
Observable.call(this);
}
Kefir.Stream = Stream;
inherit(Stream, Observable, {
_name: 'stream'
});
// Property
function Property() {
Observable.call(this);
this._current = NOTHING;
this._currentError = NOTHING;
}
Kefir.Property = Property;
inherit(Property, Observable, {
_name: 'property',
_send: function(type, x, isCurrent) {
if (this._alive) {
if (!isCurrent) {
this._dispatcher.dispatch(Event(type, x));
}
if (type === VALUE) {
this._current = x;
}
if (type === ERROR) {
this._currentError = x;
}
if (type === END) {
this._clear();
}
}
},
_on: function(type, fn) {
if (this._alive) {
this._dispatcher.add(type, fn);
this._setActive(true);
}
if (this._current !== NOTHING) {
callSubscriber(type, fn, Event(VALUE, this._current, true));
}
if (this._currentError !== NOTHING) {
callSubscriber(type, fn, Event(ERROR, this._currentError, true));
}
if (!this._alive) {
callSubscriber(type, fn, CURRENT_END);
}
return this;
}
});
// Log
Observable.prototype.log = function(name) {
name = name || this.toString();
var handler = function(event) {
var typeStr = '<' + event.type + (event.current ? ':current' : '') + '>';
if (event.type === VALUE || event.type === ERROR) {
console.log(name, typeStr, event.value);
} else {
console.log(name, typeStr);
}
};
if (!this.__logHandlers) {
this.__logHandlers = [];
}
this.__logHandlers.push({name: name, handler: handler});
this.onAny(handler);
return this;
};
Observable.prototype.offLog = function(name) {
name = name || this.toString();
if (this.__logHandlers) {
var handlerIndex = findByPred(this.__logHandlers, function(obj) {
return obj.name === name;
});
if (handlerIndex !== -1) {
var handler = this.__logHandlers[handlerIndex].handler;
this.__logHandlers.splice(handlerIndex, 1);
this.offAny(handler);
}
}
return this;
};
// Kefir.withInterval()
withInterval('withInterval', {
_init: function(args) {
this._fn = args[0];
var $ = this;
this._emitter = {
emit: function(x) {
$._send(VALUE, x);
},
error: function(x) {
$._send(ERROR, x);
},
end: function() {
$._send(END);
},
emitEvent: function(e) {
$._send(e.type, e.value);
}
};
},
_free: function() {
this._fn = null;
this._emitter = null;
},
_onTick: function() {
this._fn(this._emitter);
}
});
// Kefir.fromPoll()
withInterval('fromPoll', {
_init: function(args) {
this._fn = args[0];
},
_free: function() {
this._fn = null;
},
_onTick: function() {
this._send(VALUE, this._fn());
}
});
// Kefir.interval()
withInterval('interval', {
_init: function(args) {
this._x = args[0];
},
_free: function() {
this._x = null;
},
_onTick: function() {
this._send(VALUE, this._x);
}
});
// Kefir.sequentially()
withInterval('sequentially', {
_init: function(args) {
this._xs = cloneArray(args[0]);
if (this._xs.length === 0) {
this._send(END);
}
},
_free: function() {
this._xs = null;
},
_onTick: function() {
switch (this._xs.length) {
case 1:
this._send(VALUE, this._xs[0]);
this._send(END);
break;
default:
this._send(VALUE, this._xs.shift());
}
}
});
// Kefir.repeatedly()
withInterval('repeatedly', {
_init: function(args) {
this._xs = cloneArray(args[0]);
this._i = -1;
},
_onTick: function() {
if (this._xs.length > 0) {
this._i = (this._i + 1) % this._xs.length;
this._send(VALUE, this._xs[this._i]);
}
}
});
Kefir.repeatedly = deprecated(
'Kefir.repeatedly()',
'Kefir.repeat(() => Kefir.sequentially(...)})',
Kefir.repeatedly
);
// Kefir.later()
withInterval('later', {
_init: function(args) {
this._x = args[0];
},
_free: function() {
this._x = null;
},
_onTick: function() {
this._send(VALUE, this._x);
this._send(END);
}
});
function _AbstractPool(options) {
Stream.call(this);
this._queueLim = get(options, 'queueLim', 0);
this._concurLim = get(options, 'concurLim', -1);
this._drop = get(options, 'drop', 'new');
if (this._concurLim === 0) {
throw new Error('options.concurLim can\'t be 0');
}
var $ = this;
this._$handleSubAny = function(event) {
$._handleSubAny(event);
};
this._queue = [];
this._curSources = [];
this._activating = false;
this._bindedEndHandlers = [];
}
inherit(_AbstractPool, Stream, {
_name: 'abstractPool',
_add: function(obj, toObs) {
toObs = toObs || id;
if (this._concurLim === -1 || this._curSources.length < this._concurLim) {
this._addToCur(toObs(obj));
} else {
if (this._queueLim === -1 || this._queue.length < this._queueLim) {
this._addToQueue(toObs(obj));
} else if (this._drop === 'old') {
this._removeOldest();
this._add(toObs(obj));
}
}
},
_addAll: function(obss) {
var $ = this;
forEach(obss, function(obs) {
$._add(obs);
});
},
_remove: function(obs) {
if (this._removeCur(obs) === -1) {
this._removeQueue(obs);
}
},
_addToQueue: function(obs) {
this._queue = concat(this._queue, [obs]);
},
_addToCur: function(obs) {
this._curSources = concat(this._curSources, [obs]);
if (this._active) {
this._subscribe(obs);
}
},
_subscribe: function(obs) {
var $ = this;
var onEnd = function() {
$._removeCur(obs);
};
this._bindedEndHandlers.push({obs: obs, handler: onEnd});
obs.onAny(this._$handleSubAny);
obs.onEnd(onEnd);
},
_unsubscribe: function(obs) {
obs.offAny(this._$handleSubAny);
var onEndI = findByPred(this._bindedEndHandlers, function(obj) {
return obj.obs === obs;
});
if (onEndI !== -1) {
var onEnd = this._bindedEndHandlers[onEndI].handler;
this._bindedEndHandlers.splice(onEndI, 1);
obs.offEnd(onEnd);
}
},
_handleSubAny: function(event) {
if (event.type === VALUE || event.type === ERROR) {
this._send(event.type, event.value, event.current && this._activating);
}
},
_removeQueue: function(obs) {
var index = find(this._queue, obs);
this._queue = remove(this._queue, index);
return index;
},
_removeCur: function(obs) {
if (this._active) {
this._unsubscribe(obs);
}
var index = find(this._curSources, obs);
this._curSources = remove(this._curSources, index);
if (index !== -1) {
if (this._queue.length !== 0) {
this._pullQueue();
} else if (this._curSources.length === 0) {
this._onEmpty();
}
}
return index;
},
_removeOldest: function() {
this._removeCur(this._curSources[0]);
},
_pullQueue: function() {
if (this._queue.length !== 0) {
this._queue = cloneArray(this._queue);
this._addToCur(this._queue.shift());
}
},
_onActivation: function() {
var sources = this._curSources
, i;
this._activating = true;
for (i = 0; i < sources.length; i++) {
if (this._active) {
this._subscribe(sources[i]);
}
}
this._activating = false;
},
_onDeactivation: function() {
var sources = this._curSources
, i;
for (i = 0; i < sources.length; i++) {
this._unsubscribe(sources[i]);
}
},
_isEmpty: function() {
return this._curSources.length === 0;
},
_onEmpty: function() {},
_clear: function() {
Stream.prototype._clear.call(this);
this._queue = null;
this._curSources = null;
this._$handleSubAny = null;
this._bindedEndHandlers = null;
}
});
// .merge()
var MergeLike = {
_onEmpty: function() {
if (this._initialised) {
this._send(END, null, this._activating);
}
}
};
function Merge(sources) {
_AbstractPool.call(this);
if (sources.length === 0) {
this._send(END);
} else {
this._addAll(sources);
}
this._initialised = true;
}
inherit(Merge, _AbstractPool, extend({_name: 'merge'}, MergeLike));
Kefir.merge = function(obss) {
return new Merge(obss);
};
Observable.prototype.merge = function(other) {
return Kefir.merge([this, other]);
};
// .concat()
function Concat(sources) {
_AbstractPool.call(this, {concurLim: 1, queueLim: -1});
if (sources.length === 0) {
this._send(END);
} else {
this._addAll(sources);
}
this._initialised = true;
}
inherit(Concat, _AbstractPool, extend({_name: 'concat'}, MergeLike));
Kefir.concat = function(obss) {
return new Concat(obss);
};
Observable.prototype.concat = function(other) {
return Kefir.concat([this, other]);
};
// .pool()
function Pool() {
_AbstractPool.call(this);
}
Kefir.Pool = Pool;
inherit(Pool, _AbstractPool, {
_name: 'pool',
plug: function(obs) {
this._add(obs);
return this;
},
unplug: function(obs) {
this._remove(obs);
return this;
}
});
Kefir.pool = function() {
return new Pool();
};
// .bus()
function Bus() {
_AbstractPool.call(this);
}
Kefir.Bus = Bus;
inherit(Bus, _AbstractPool, {
_name: 'bus',
plug: function(obs) {
this._add(obs);
return this;
},
unplug: function(obs) {
this._remove(obs);
return this;
},
emit: function(x) {
this._send(VALUE, x);
return this;
},
error: function(x) {
this._send(ERROR, x);
return this;
},
end: function() {
this._send(END);
return this;
},
emitEvent: function(event) {
this._send(event.type, event.value);
}
});
Kefir.bus = function() {
return new Bus();
};
// .flatMap()
function FlatMap(source, fn, options) {
_AbstractPool.call(this, options);
this._source = source;
this._fn = fn || id;
this._mainEnded = false;
this._lastCurrent = null;
var $ = this;
this._$handleMainSource = function(event) {
$._handleMainSource(event);
};
}
inherit(FlatMap, _AbstractPool, {
_onActivation: function() {
_AbstractPool.prototype._onActivation.call(this);
if (this._active) {
this._activating = true;
this._source.onAny(this._$handleMainSource);
this._activating = false;
}
},
_onDeactivation: function() {
_AbstractPool.prototype._onDeactivation.call(this);
this._source.offAny(this._$handleMainSource);
},
_handleMainSource: function(event) {
if (event.type === VALUE) {
if (!event.current || this._lastCurrent !== event.value) {
this._add(event.value, this._fn);
}
this._lastCurrent = event.value;
}
if (event.type === ERROR) {
this._send(ERROR, event.value, event.current);
}
if (event.type === END) {
if (this._isEmpty()) {
this._send(END, null, event.current);
} else {
this._mainEnded = true;
}
}
},
_onEmpty: function() {
if (this._mainEnded) {
this._send(END);
}
},
_clear: function() {
_AbstractPool.prototype._clear.call(this);
this._source = null;
this._lastCurrent = null;
this._$handleMainSource = null;
}
});
Observable.prototype.flatMap = function(fn) {
return new FlatMap(this, fn)
.setName(this, 'flatMap');
};
Observable.prototype.flatMapLatest = function(fn) {
return new FlatMap(this, fn, {concurLim: 1, drop: 'old'})
.setName(this, 'flatMapLatest');
};
Observable.prototype.flatMapFirst = function(fn) {
return new FlatMap(this, fn, {concurLim: 1})
.setName(this, 'flatMapFirst');
};
Observable.prototype.flatMapConcat = function(fn) {
return new FlatMap(this, fn, {queueLim: -1, concurLim: 1})
.setName(this, 'flatMapConcat');
};
Observable.prototype.flatMapConcurLimit = function(fn, limit) {
var result;
if (limit === 0) {
result = Kefir.never();
} else {
if (limit < 0) {
limit = -1;
}
result = new FlatMap(this, fn, {queueLim: -1, concurLim: limit});
}
return result.setName(this, 'flatMapConcurLimit');
};
// .zip()
function Zip(sources, combinator) {
Stream.call(this);
if (sources.length === 0) {
this._send(END);
} else {
this._buffers = map(sources, function(source) {
return isArray(source) ? cloneArray(source) : [];
});
this._sources = map(sources, function(source) {
return isArray(source) ? Kefir.never() : source;
});
this._combinator = combinator ? spread(combinator, this._sources.length) : id;
this._aliveCount = 0;
this._bindedHandlers = Array(this._sources.length);
for (var i = 0; i < this._sources.length; i++) {
this._bindedHandlers[i] = this._bindHandleAny(i);
}
}
}
inherit(Zip, Stream, {
_name: 'zip',
_onActivation: function() {
var i, length = this._sources.length;
this._drainArrays();
this._aliveCount = length;
for (i = 0; i < length; i++) {
if (this._active) {
this._sources[i].onAny(this._bindedHandlers[i]);
}
}
},
_onDeactivation: function() {
for (var i = 0; i < this._sources.length; i++) {
this._sources[i].offAny(this._bindedHandlers[i]);
}
},
_emit: function(isCurrent) {
var values = new Array(this._buffers.length);
for (var i = 0; i < this._buffers.length; i++) {
values[i] = this._buffers[i].shift();
}
this._send(VALUE, this._combinator(values), isCurrent);
},
_isFull: function() {
for (var i = 0; i < this._buffers.length; i++) {
if (this._buffers[i].length === 0) {
return false;
}
}
return true;
},
_emitIfFull: function(isCurrent) {
if (this._isFull()) {
this._emit(isCurrent);
}
},
_drainArrays: function() {
while (this._isFull()) {
this._emit(true);
}
},
_bindHandleAny: function(i) {
var $ = this;
return function(event) {
$._handleAny(i, event);
};
},
_handleAny: function(i, event) {
if (event.type === VALUE) {
this._buffers[i].push(event.value);
this._emitIfFull(event.current);
}
if (event.type === ERROR) {
this._send(ERROR, event.value, event.current);
}
if (event.type === END) {
this._aliveCount--;
if (this._aliveCount === 0) {
this._send(END, null, event.current);
}
}
},
_clear: function() {
Stream.prototype._clear.call(this);
this._sources = null;
this._buffers = null;
this._combinator = null;
this._bindedHandlers = null;
}
});
Kefir.zip = function(sources, combinator) {
return new Zip(sources, combinator);
};
Observable.prototype.zip = function(other, combinator) {
return new Zip([this, other], combinator);
};
// .combine()
function Combine(active, passive, combinator) {
Stream.call(this);
if (active.length === 0) {
this._send(END);
} else {
this._activeCount = active.length;
this._sources = concat(active, passive);
this._combinator = combinator ? spread(combinator, this._sources.length) : id;
this._aliveCount = 0;
this._currents = new Array(this._sources.length);
fillArray(this._currents, NOTHING);
this._activating = false;
this._emitAfterActivation = false;
this._endAfterActivation = false;
this._bindedHandlers = Array(this._sources.length);
for (var i = 0; i < this._sources.length; i++) {
this._bindedHandlers[i] = this._bindHandleAny(i);
}
}
}
inherit(Combine, Stream, {
_name: 'combine',
_onActivation: function() {
var length = this._sources.length,
i;
this._aliveCount = this._activeCount;
this._activating = true;
for (i = 0; i < length; i++) {
this._sources[i].onAny(this._bindedHandlers[i]);
}
this._activating = false;
if (this._emitAfterActivation) {
this._emitAfterActivation = false;
this._emitIfFull(true);
}
if (this._endAfterActivation) {
this._send(END, null, true);
}
},
_onDeactivation: function() {
var length = this._sources.length,
i;
for (i = 0; i < length; i++) {
this._sources[i].offAny(this._bindedHandlers[i]);
}
},
_emitIfFull: function(isCurrent) {
if (!contains(this._currents, NOTHING)) {
var combined = cloneArray(this._currents);
combined = this._combinator(combined);
this._send(VALUE, combined, isCurrent);
}
},
_bindHandleAny: function(i) {
var $ = this;
return function(event) {
$._handleAny(i, event);
};
},
_handleAny: function(i, event) {
if (event.type === VALUE) {
this._currents[i] = event.value;
if (i < this._activeCount) {
if (this._activating) {
this._emitAfterActivation = true;
} else {
this._emitIfFull(event.current);
}
}
}
if (event.type === ERROR) {
this._send(ERROR, event.value, event.current);
}
if (event.type === END) {
if (i < this._activeCount) {
this._aliveCount--;
if (this._aliveCount === 0) {
if (this._activating) {
this._endAfterActivation = true;
} else {
this._send(END, null, event.current);
}
}
}
}
},
_clear: function() {
Stream.prototype._clear.call(this);
this._sources = null;
this._currents = null;
this._combinator = null;
this._bindedHandlers = null;
}
});
Kefir.combine = function(active, passive, combinator) {
if (isFn(passive)) {
combinator = passive;
passive = null;
}
return new Combine(active, passive || [], combinator);
};
Observable.prototype.combine = function(other, combinator) {
return Kefir.combine([this, other], combinator);
};
// .sampledBy()
Kefir.sampledBy = deprecated(
'Kefir.sampledBy()',
'Kefir.combine(active, passive, combinator)',
function(passive, active, combinator) {
// we need to flip `passive` and `active` in combinator function
var _combinator = combinator;
if (passive.length > 0) {
var passiveLength = passive.length;
_combinator = function() {
var args = circleShift(arguments, passiveLength);
return combinator ? apply(combinator, null, args) : args;
};
}
return new Combine(active, passive, _combinator).setName('sampledBy');
}
);
Observable.prototype.sampledBy = function(other, combinator) {
var _combinator;
if (combinator) {
_combinator = function(active, passive) {
return combinator(passive, active);
};
}
return new Combine([other], [this], _combinator || id2).setName(this, 'sampledBy');
};
function produceStream(StreamClass, PropertyClass) {
return function() {
return new StreamClass(this, arguments);
};
}
function produceProperty(StreamClass, PropertyClass) {
return function() {
return new PropertyClass(this, arguments);
};
}
// .toProperty()
withOneSource('toProperty', {
_init: function(args) {
if (args.length > 0) {
this._send(VALUE, args[0]);
}
}
}, {propertyMethod: produceProperty, streamMethod: produceProperty});
// .changes()
withOneSource('changes', {
_handleValue: function(x, isCurrent) {
if (!isCurrent) {
this._send(VALUE, x);
}
},
_handleError: function(x, isCurrent) {
if (!isCurrent) {
this._send(ERROR, x);
}
}
}, {
streamMethod: function() {
return function() {
return this;
};
},
propertyMethod: produceStream
});
// .withHandler()
withOneSource('withHandler', {
_init: function(args) {
this._handler = args[0];
this._forcedCurrent = false;
var $ = this;
this._emitter = {
emit: function(x) {
$._send(VALUE, x, $._forcedCurrent);
},
error: function(x) {
$._send(ERROR, x, $._forcedCurrent);
},
end: function() {
$._send(END, null, $._forcedCurrent);
},
emitEvent: function(e) {
$._send(e.type, e.value, $._forcedCurrent);
}
};
},
_free: function() {
this._handler = null;
this._emitter = null;
},
_handleAny: function(event) {
this._forcedCurrent = event.current;
this._handler(this._emitter, event);
this._forcedCurrent = false;
}
});
// .flatten(fn)
withOneSource('flatten', {
_init: function(args) {
this._fn = args[0] ? args[0] : id;
},
_free: function() {
this._fn = null;
},
_handleValue: function(x, isCurrent) {
var xs = this._fn(x);
for (var i = 0; i < xs.length; i++) {
this._send(VALUE, xs[i], isCurrent);
}
}
});
// .transduce(transducer)
function xformForObs(obs) {
return {
step: function(res, input) {
obs._send(VALUE, input, obs._forcedCurrent);
return null;
},
result: function(res) {
obs._send(END, null, obs._forcedCurrent);
return null;
}
};
}
withOneSource('transduce', {
_init: function(args) {
this._xform = args[0](xformForObs(this));
},
_free: function() {
this._xform = null;
},
_handleValue: function(x, isCurrent) {
this._forcedCurrent = isCurrent;
if (this._xform.step(null, x) !== null) {
this._xform.result(null);
}
this._forcedCurrent = false;
},
_handleEnd: function(__, isCurrent) {
this._forcedCurrent = isCurrent;
this._xform.result(null);
this._forcedCurrent = false;
}
});
// .map(fn)
withOneSource('map', {
_init: function(args) {
this._fn = args[0] || id;
},
_free: function() {
this._fn = null;
},
_handleValue: function(x, isCurrent) {
this._send(VALUE, this._fn(x), isCurrent);
}
});
// .mapErrors(fn)
withOneSource('mapErrors', {
_init: function(args) {
this._fn = args[0] || id;
},
_free: function() {
this._fn = null;
},
_handleError: function(x, isCurrent) {
this._send(ERROR, this._fn(x), isCurrent);
}
});
// .errorsToValues(fn)
function defaultErrorsToValuesHandler(x) {
return {
convert: true,
value: x
};
}
withOneSource('errorsToValues', {
_init: function(args) {
this._fn = args[0] || defaultErrorsToValuesHandler;
},
_free: function() {
this._fn = null;
},
_handleError: function(x, isCurrent) {
var result = this._fn(x);
var type = result.convert ? VALUE : ERROR;
var newX = result.convert ? result.value : x;
this._send(type, newX, isCurrent);
}
});
// .valuesToErrors(fn)
function defaultValuesToErrorsHandler(x) {
return {
convert: true,
error: x
};
}
withOneSource('valuesToErrors', {
_init: function(args) {
this._fn = args[0] || defaultValuesToErrorsHandler;
},
_free: function() {
this._fn = null;
},
_handleValue: function(x, isCurrent) {
var result = this._fn(x);
var type = result.convert ? ERROR : VALUE;
var newX = result.convert ? result.error : x;
this._send(type, newX, isCurrent);
}
});
// .filter(fn)
withOneSource('filter', {
_init: function(args) {
this._fn = args[0] || id;
},
_free: function() {
this._fn = null;
},
_handleValue: function(x, isCurrent) {
if (this._fn(x)) {
this._send(VALUE, x, isCurrent);
}
}
});
// .filterErrors(fn)
withOneSource('filterErrors', {
_init: function(args) {
this._fn = args[0] || id;
},
_free: function() {
this._fn = null;
},
_handleError: function(x, isCurrent) {
if (this._fn(x)) {
this._send(ERROR, x, isCurrent);
}
}
});
// .takeWhile(fn)
withOneSource('takeWhile', {
_init: function(args) {
this._fn = args[0] || id;
},
_free: function() {
this._fn = null;
},
_handleValue: function(x, isCurrent) {
if (this._fn(x)) {
this._send(VALUE, x, isCurrent);
} else {
this._send(END, null, isCurrent);
}
}
});
// .take(n)
withOneSource('take', {
_init: function(args) {
this._n = args[0];
if (this._n <= 0) {
this._send(END);
}
},
_handleValue: function(x, isCurrent) {
this._n--;
this._send(VALUE, x, isCurrent);
if (this._n === 0) {
this._send(END, null, isCurrent);
}
}
});
// .skip(n)
withOneSource('skip', {
_init: function(args) {
this._n = Math.max(0, args[0]);
},
_handleValue: function(x, isCurrent) {
if (this._n === 0) {
this._send(VALUE, x, isCurrent);
} else {
this._n--;
}
}
});
// .skipDuplicates([fn])
withOneSource('skipDuplicates', {
_init: function(args) {
this._fn = args[0] || strictEqual;
this._prev = NOTHING;
},
_free: function() {
this._fn = null;
this._prev = null;
},
_handleValue: function(x, isCurrent) {
if (this._prev === NOTHING || !this._fn(this._prev, x)) {
this._prev = x;
this._send(VALUE, x, isCurrent);
}
}
});
// .skipWhile(fn)
withOneSource('skipWhile', {
_init: function(args) {
this._fn = args[0] || id;
this._skip = true;
},
_free: function() {
this._fn = null;
},
_handleValue: function(x, isCurrent) {
if (!this._skip) {
this._send(VALUE, x, isCurrent);
return;
}
if (!this._fn(x)) {
this._skip = false;
this._fn = null;
this._send(VALUE, x, isCurrent);
}
}
});
// .diff(fn, seed)
withOneSource('diff', {
_init: function(args) {
this._fn = args[0] || defaultDiff;
this._prev = args.length > 1 ? args[1] : NOTHING;
},
_free: function() {
this._prev = null;
this._fn = null;
},
_handleValue: function(x, isCurrent) {
if (this._prev !== NOTHING) {
this._send(VALUE, this._fn(this._prev, x), isCurrent);
}
this._prev = x;
}
});
// .scan(fn, seed)
withOneSource('scan', {
_init: function(args) {
this._fn = args[0];
if (args.length > 1) {
this._send(VALUE, args[1], true);
}
},
_free: function() {
this._fn = null;
},
_handleValue: function(x, isCurrent) {
if (this._current !== NOTHING) {
x = this._fn(this._current, x);
}
this._send(VALUE, x, isCurrent);
}
}, {streamMethod: produceProperty});
// .reduce(fn, seed)
withOneSource('reduce', {
_init: function(args) {
this._fn = args[0];
this._result = args.length > 1 ? args[1] : NOTHING;
},
_free: function() {
this._fn = null;
this._result = null;
},
_handleValue: function(x) {
this._result = (this._result === NOTHING) ? x : this._fn(this._result, x);
},
_handleEnd: function(__, isCurrent) {
if (this._result !== NOTHING) {
this._send(VALUE, this._result, isCurrent);
}
this._send(END, null, isCurrent);
}
});
// .mapEnd(fn)
withOneSource('mapEnd', {
_init: function(args) {
this._fn = args[0];
},
_free: function() {
this._fn = null;
},
_handleEnd: function(__, isCurrent) {
this._send(VALUE, this._fn(), isCurrent);
this._send(END, null, isCurrent);
}
});
// .skipValue()
withOneSource('skipValues', {
_handleValue: function() {}
});
// .skipError()
withOneSource('skipErrors', {
_handleError: function() {}
});
// .skipEnd()
withOneSource('skipEnd', {
_handleEnd: function() {}
});
// .endOnError()
withOneSource('endOnError', {
_handleError: function(x, isCurrent) {
this._send(ERROR, x, isCurrent);
this._send(END, null, isCurrent);
}
});
// .slidingWindow(max[, min])
withOneSource('slidingWindow', {
_init: function(args) {
this._max = args[0];
this._min = args[1] || 0;
this._buff = [];
},
_free: function() {
this._buff = null;
},
_handleValue: function(x, isCurrent) {
this._buff = slide(this._buff, x, this._max);
if (this._buff.length >= this._min) {
this._send(VALUE, this._buff, isCurrent);
}
}
});
// .bufferWhile([predicate], [options])
withOneSource('bufferWhile', {
_init: function(args) {
this._fn = args[0] || id;
this._flushOnEnd = get(args[1], 'flushOnEnd', true);
this._buff = [];
},
_free: function() {
this._buff = null;
},
_flush: function(isCurrent) {
if (this._buff !== null && this._buff.length !== 0) {
this._send(VALUE, this._buff, isCurrent);
this._buff = [];
}
},
_handleValue: function(x, isCurrent) {
this._buff.push(x);
if (!this._fn(x)) {
this._flush(isCurrent);
}
},
_handleEnd: function(x, isCurrent) {
if (this._flushOnEnd) {
this._flush(isCurrent);
}
this._send(END, null, isCurrent);
}
});
// .debounce(wait, {immediate})
withOneSource('debounce', {
_init: function(args) {
this._wait = Math.max(0, args[0]);
this._immediate = get(args[1], 'immediate', false);
this._lastAttempt = 0;
this._timeoutId = null;
this._laterValue = null;
this._endLater = false;
var $ = this;
this._$later = function() {
$._later();
};
},
_free: function() {
this._laterValue = null;
this._$later = null;
},
_handleValue: function(x, isCurrent) {
if (isCurrent) {
this._send(VALUE, x, isCurrent);
} else {
this._lastAttempt = now();
if (this._immediate && !this._timeoutId) {
this._send(VALUE, x);
}
if (!this._timeoutId) {
this._timeoutId = setTimeout(this._$later, this._wait);
}
if (!this._immediate) {
this._laterValue = x;
}
}
},
_handleEnd: function(__, isCurrent) {
if (isCurrent) {
this._send(END, null, isCurrent);
} else {
if (this._timeoutId && !this._immediate) {
this._endLater = true;
} else {
this._send(END);
}
}
},
_later: function() {
var last = now() - this._lastAttempt;
if (last < this._wait && last >= 0) {
this._timeoutId = setTimeout(this._$later, this._wait - last);
} else {
this._timeoutId = null;
if (!this._immediate) {
this._send(VALUE, this._laterValue);
this._laterValue = null;
}
if (this._endLater) {
this._send(END);
}
}
}
});
// .throttle(wait, {leading, trailing})
withOneSource('throttle', {
_init: function(args) {
this._wait = Math.max(0, args[0]);
this._leading = get(args[1], 'leading', true);
this._trailing = get(args[1], 'trailing', true);
this._trailingValue = null;
this._timeoutId = null;
this._endLater = false;
this._lastCallTime = 0;
var $ = this;
this._$trailingCall = function() {
$._trailingCall();
};
},
_free: function() {
this._trailingValue = null;
this._$trailingCall = null;
},
_handleValue: function(x, isCurrent) {
if (isCurrent) {
this._send(VALUE, x, isCurrent);
} else {
var curTime = now();
if (this._lastCallTime === 0 && !this._leading) {
this._lastCallTime = curTime;
}
var remaining = this._wait - (curTime - this._lastCallTime);
if (remaining <= 0) {
this._cancelTraling();
this._lastCallTime = curTime;
this._send(VALUE, x);
} else if (this._trailing) {
this._cancelTraling();
this._trailingValue = x;
this._timeoutId = setTimeout(this._$trailingCall, remaining);
}
}
},
_handleEnd: function(__, isCurrent) {
if (isCurrent) {
this._send(END, null, isCurrent);
} else {
if (this._timeoutId) {
this._endLater = true;
} else {
this._send(END);
}
}
},
_cancelTraling: function() {
if (this._timeoutId !== null) {
clearTimeout(this._timeoutId);
this._timeoutId = null;
}
},
_trailingCall: function() {
this._send(VALUE, this._trailingValue);
this._timeoutId = null;
this._trailingValue = null;
this._lastCallTime = !this._leading ? 0 : now();
if (this._endLater) {
this._send(END);
}
}
});
// .delay()
withOneSource('delay', {
_init: function(args) {
this._wait = Math.max(0, args[0]);
this._buff = [];
var $ = this;
this._$shiftBuff = function() {
$._send(VALUE, $._buff.shift());
};
},
_free: function() {
this._buff = null;
this._$shiftBuff = null;
},
_handleValue: function(x, isCurrent) {
if (isCurrent) {
this._send(VALUE, x, isCurrent);
} else {
this._buff.push(x);
setTimeout(this._$shiftBuff, this._wait);
}
},
_handleEnd: function(__, isCurrent) {
if (isCurrent) {
this._send(END, null, isCurrent);
} else {
var $ = this;
setTimeout(function() {
$._send(END);
}, this._wait);
}
}
});
// Kefir.fromBinder(fn)
function FromBinder(fn) {
Stream.call(this);
this._fn = fn;
this._unsubscribe = null;
}
inherit(FromBinder, Stream, {
_name: 'fromBinder',
_onActivation: function() {
var $ = this
, isCurrent = true
, emitter = {
emit: function(x) {
$._send(VALUE, x, isCurrent);
},
error: function(x) {
$._send(ERROR, x, isCurrent);
},
end: function() {
$._send(END, null, isCurrent);
},
emitEvent: function(e) {
$._send(e.type, e.value, isCurrent);
}
};
this._unsubscribe = this._fn(emitter) || null;
// fix https://github.com/pozadi/kefir/issues/35
if (!this._active && this._unsubscribe !== null) {
this._unsubscribe();
this._unsubscribe = null;
}
isCurrent = false;
},
_onDeactivation: function() {
if (this._unsubscribe !== null) {
this._unsubscribe();
this._unsubscribe = null;
}
},
_clear: function() {
Stream.prototype._clear.call(this);
this._fn = null;
}
});
Kefir.fromBinder = function(fn) {
return new FromBinder(fn);
};
// Kefir.emitter()
function Emitter() {
Stream.call(this);
}
inherit(Emitter, Stream, {
_name: 'emitter',
emit: function(x) {
this._send(VALUE, x);
return this;
},
error: function(x) {
this._send(ERROR, x);
return this;
},
end: function() {
this._send(END);
return this;
},
emitEvent: function(event) {
this._send(event.type, event.value);
}
});
Kefir.emitter = function() {
return new Emitter();
};
Kefir.Emitter = Emitter;