baconjs
Version:
A small functional reactive programming lib for JavaScript.
1,789 lines (1,738 loc) • 161 kB
JavaScript
/** @hidden */
function nop() { }
/** @hidden */
const isArray = Array.isArray || function (xs) { return xs instanceof Array; };
/** @hidden */
function isObservable(x) {
return x && x._isObservable;
}
/** @hidden */
function all(xs, f) {
for (var i = 0, x; i < xs.length; i++) {
x = xs[i];
if (!f(x)) {
return false;
}
}
return true;
}
/** @hidden */
function always(x) { return () => x; }
/** @hidden */
function any(xs, f) {
for (var i = 0, x; i < xs.length; i++) {
x = xs[i];
if (f(x)) {
return true;
}
}
return false;
}
/** @hidden */
function bind(fn, me) {
return function () { return fn.apply(me, arguments); };
}
/** @hidden */
function contains(xs, x) { return indexOf(xs, x) !== -1; }
/** @hidden */
function each(xs, f) {
for (var key in xs) {
if (Object.prototype.hasOwnProperty.call(xs, key)) {
var value = xs[key];
f(key, value);
}
}
}
/** @hidden */
function empty(xs) { return xs.length === 0; }
/** @hidden */
function filter(f, xs) {
var filtered = [];
for (var i = 0, x; i < xs.length; i++) {
x = xs[i];
if (f(x)) {
filtered.push(x);
}
}
return filtered;
}
/** @hidden */
function flatMap(f, xs) {
return fold(xs, [], (function (ys, x) {
return ys.concat(f(x));
}));
}
/** @hidden */
function flip(f) {
return (a, b) => f(b, a);
}
/** @hidden */
function fold(xs, seed, f) {
for (var i = 0, x; i < xs.length; i++) {
x = xs[i];
seed = f(seed, x);
}
return seed;
}
/** @hidden */
function head(xs) {
return xs[0];
}
/** @hidden */
function id(x) { return x; }
/** @hidden */
function indexOfDefault(xs, x) { return xs.indexOf(x); }
/** @hidden */
function indexOfFallback(xs, x) {
for (var i = 0, y; i < xs.length; i++) {
y = xs[i];
if (x === y) {
return i;
}
}
return -1;
}
/** @hidden */
const indexOf = Array.prototype.indexOf ? indexOfDefault : indexOfFallback;
/** @hidden */
function indexWhere(xs, f) {
for (var i = 0, y; i < xs.length; i++) {
y = xs[i];
if (f(y)) {
return i;
}
}
return -1;
}
/** @hidden */
function isFunction(f) { return typeof f === "function"; }
/** @hidden */
function last(xs) { return xs[xs.length - 1]; }
/** @hidden */
function map(f, xs) {
var result = [];
for (var i = 0, x; i < xs.length; i++) {
x = xs[i];
result.push(f(x));
}
return result;
}
/** @hidden */
function negate(f) { return function (x) { return !f(x); }; }
/** @hidden */
function remove(x, xs) {
var i = indexOf(xs, x);
if (i >= 0) {
return xs.splice(i, 1);
}
}
/** @hidden */
function tail(xs) {
return xs.slice(1, xs.length);
}
/** @hidden */
function toArray(xs) { return (isArray(xs) ? xs : [xs]); }
/** @hidden */
function toFunction(f) {
if (typeof f == "function") {
return f;
}
return x => f;
}
/** @hidden */
function toString(obj) {
var hasProp = {}.hasOwnProperty;
try {
recursionDepth++;
if (obj == null) {
return "undefined";
}
else if (isFunction(obj)) {
return "function";
}
else if (isArray(obj)) {
if (recursionDepth > 5) {
return "[..]";
}
return "[" + map(toString, obj).toString() + "]";
}
else if (((obj != null ? obj.toString : void 0) != null) && obj.toString !== Object.prototype.toString) {
return obj.toString();
}
else if (typeof obj === "object") {
if (recursionDepth > 5) {
return "{..}";
}
var results = [];
for (var key in obj) {
if (!hasProp.call(obj, key))
continue;
let value = (function () {
try {
return obj[key];
}
catch (error) {
return error;
}
})();
results.push(toString(key) + ":" + toString(value));
}
return "{" + results + "}";
}
else {
return obj;
}
}
finally {
recursionDepth--;
}
}
/** @hidden */
function without(x, xs) {
return filter((function (y) { return y !== x; }), xs);
}
var _ = {
indexOf,
indexWhere,
head,
always,
negate,
empty,
tail,
filter,
map,
each,
toArray,
contains,
id,
last,
all,
any,
without,
remove,
fold,
flatMap,
bind,
isFunction,
toFunction,
toString
};
var recursionDepth = 0;
/**
* Reply for "more data, please".
*/
const more = undefined;
/**
* Reply for "no more data, please".
*/
const noMore = "<no-more>";
/** @hidden */
function assert(message, condition) {
if (!condition) {
throw new Error(message);
}
}
/** @hidden */
function assertEventStream(event) {
if (!(event != null ? event._isEventStream : void 0)) {
throw new Error("not an EventStream : " + event);
}
}
/** @hidden */
function assertObservable(observable) {
if (!(observable != null ? observable._isObservable : void 0)) {
throw new Error("not an Observable : " + observable);
}
}
/** @hidden */
function assertFunction(f) {
return assert("not a function : " + f, _.isFunction(f));
}
/** @hidden */
function assertArray(xs) {
if (!isArray(xs)) {
throw new Error("not an array : " + xs);
}
}
/** @hidden */
function assertNoArguments(args) {
return assert("no arguments supported", args.length === 0);
}
/** @hidden */
const defaultScheduler = {
setTimeout(f, d) { return setTimeout(f, d); },
setInterval(f, i) { return setInterval(f, i); },
clearInterval(id) { return clearInterval(id); },
clearTimeout(id) { return clearTimeout(id); },
now() { return new Date().getTime(); }
};
const GlobalScheduler = {
scheduler: defaultScheduler
};
function getScheduler() {
return GlobalScheduler.scheduler;
}
function setScheduler(newScheduler) {
GlobalScheduler.scheduler = newScheduler;
}
var rootEvent = undefined;
var waiterObs = [];
var waiters = {};
var aftersStack = [];
var aftersStackHeight = 0;
var flushed = {};
var processingAfters = false;
function toString$1() {
return _.toString({ rootEvent, processingAfters, waiterObs, waiters, aftersStack, aftersStackHeight, flushed });
}
function ensureStackHeight(h) {
if (h <= aftersStackHeight)
return;
if (!aftersStack[h - 1]) {
aftersStack[h - 1] = [[], 0];
}
aftersStackHeight = h;
}
function isInTransaction() {
return rootEvent !== undefined;
}
function soonButNotYet(obs, f) {
if (rootEvent) {
// If in transaction -> perform within transaction
//console.log('in tx')
whenDoneWith(obs, f);
}
else {
// Otherwise -> perform with timeout
//console.log('with timeout')
GlobalScheduler.scheduler.setTimeout(f, 0);
}
}
function afterTransaction(obs, f) {
if (rootEvent || processingAfters) {
ensureStackHeight(1);
var stackIndexForThisObs = 0;
while (stackIndexForThisObs < aftersStackHeight - 1) {
if (containsObs(obs, aftersStack[stackIndexForThisObs][0])) {
// this observable is already being processed at this stack index -> use this index
break;
}
stackIndexForThisObs++;
}
var listFromStack = aftersStack[stackIndexForThisObs][0];
listFromStack.push([obs, f]);
if (!rootEvent) {
processAfters(); // wouldn't be called otherwise
}
}
else {
return f();
}
}
function containsObs(obs, aftersList) {
for (var i = 0; i < aftersList.length; i++) {
if (aftersList[i][0].id == obs.id)
return true;
}
return false;
}
function processAfters() {
let stackSizeAtStart = aftersStackHeight;
if (!stackSizeAtStart)
return;
let isRoot = !processingAfters;
processingAfters = true;
try {
while (aftersStackHeight >= stackSizeAtStart) { // to prevent sinking to levels started by others
var topOfStack = aftersStack[aftersStackHeight - 1];
if (!topOfStack)
throw new Error("Unexpected stack top: " + topOfStack);
var [topAfters, index] = topOfStack;
if (index < topAfters.length) {
var [, after] = topAfters[index];
topOfStack[1]++; // increase index already here to indicate that this level is being processed
ensureStackHeight(aftersStackHeight + 1); // to ensure there's a new level for recursively added afters
var callSuccess = false;
try {
after();
callSuccess = true;
while (aftersStackHeight > stackSizeAtStart && aftersStack[aftersStackHeight - 1][0].length == 0) {
aftersStackHeight--;
}
}
finally {
if (!callSuccess) {
aftersStack = [];
aftersStackHeight = 0; // reset state to prevent stale updates after error
}
}
}
else {
topOfStack[0] = [];
topOfStack[1] = 0; // reset this level
break;
}
}
}
finally {
if (isRoot)
processingAfters = false;
}
}
function whenDoneWith(obs, f) {
if (rootEvent) {
var obsWaiters = waiters[obs.id];
if (obsWaiters === undefined) {
obsWaiters = waiters[obs.id] = [f];
return waiterObs.push(obs);
}
else {
return obsWaiters.push(f);
}
}
else {
return f();
}
}
function flush() {
while (waiterObs.length > 0) {
flushWaiters(0, true);
}
flushed = {};
}
function flushWaiters(index, deps) {
var obs = waiterObs[index];
var obsId = obs.id;
var obsWaiters = waiters[obsId];
waiterObs.splice(index, 1);
delete waiters[obsId];
if (deps && waiterObs.length > 0) {
flushDepsOf(obs);
}
for (var i = 0, f; i < obsWaiters.length; i++) {
f = obsWaiters[i];
f();
}
}
function flushDepsOf(obs) {
if (flushed[obs.id])
return;
var deps = obs.internalDeps();
for (var i = 0, dep; i < deps.length; i++) {
dep = deps[i];
flushDepsOf(dep);
if (waiters[dep.id]) {
var index = _.indexOf(waiterObs, dep);
flushWaiters(index, false);
}
}
flushed[obs.id] = true;
}
function inTransaction(event, context, f, args) {
if (rootEvent) {
//console.log("in tx")
return f.apply(context, args);
}
else {
//console.log("start tx")
rootEvent = event;
try {
var result = f.apply(context, args);
//console.log("done with tx")
flush();
}
finally {
rootEvent = undefined;
processAfters();
}
return result;
}
}
function currentEventId() {
return rootEvent ? rootEvent.id : undefined;
}
function wrappedSubscribe(obs, subscribe, sink) {
assertFunction(sink);
let unsubd = false;
let shouldUnsub = false;
let doUnsub = () => {
shouldUnsub = true;
};
let unsub = () => {
unsubd = true;
doUnsub();
};
doUnsub = subscribe(function (event) {
afterTransaction(obs, function () {
if (!unsubd) {
var reply = sink(event);
if (reply === noMore) {
unsub();
}
}
});
return more;
});
if (shouldUnsub) {
doUnsub();
}
return unsub;
}
function hasWaiters() { return waiterObs.length > 0; }
var UpdateBarrier = { toString: toString$1, whenDoneWith, hasWaiters, inTransaction, currentEventId, wrappedSubscribe, afterTransaction, soonButNotYet, isInTransaction };
class Desc {
constructor(context, method, args = []) {
/** @hidden */
this._isDesc = true;
//assert("context missing", context)
//assert("method missing", method)
//assert("args missing", args)
this.context = context;
this.method = method;
this.args = args;
}
deps() {
if (!this.cachedDeps) {
this.cachedDeps = findDeps([this.context].concat(this.args));
}
return this.cachedDeps;
}
toString() {
let args = _.map(_.toString, this.args);
return _.toString(this.context) + "." + _.toString(this.method) + "(" + args + ")";
}
}
/** @hidden */
function describe(context, method, ...args) {
const ref = context || method;
if (ref && ref._isDesc) {
return context || method;
}
else {
return new Desc(context, method, args);
}
}
/** @hidden */
function findDeps(x) {
if (isArray(x)) {
return _.flatMap(findDeps, x);
}
else if (isObservable(x)) {
return [x];
}
else if ((typeof x !== "undefined" && x !== null) ? x._isSource : undefined) {
return [x.obs];
}
else {
return [];
}
}
/** @hidden */
const nullSink = () => more;
/** @hidden */
const nullVoidSink = () => more;
/** @hidden */
function withStateMachine(initState, f, src) {
return src.transform(withStateMachineT(initState, f), new Desc(src, "withStateMachine", [initState, f]));
}
function withStateMachineT(initState, f) {
let state = initState;
return (event, sink) => {
var fromF = f(state, event);
var [newState, outputs] = fromF;
state = newState;
var reply = more;
for (var i = 0; i < outputs.length; i++) {
let output = outputs[i];
reply = sink(output);
if (reply === noMore) {
return reply;
}
}
return reply;
};
}
/** @hidden */
class Some {
constructor(value) {
this._isSome = true;
this.isDefined = true;
this.value = value;
}
getOrElse(arg) { return this.value; }
get() { return this.value; }
filter(f) {
if (f(this.value)) {
return new Some(this.value);
}
else {
return None;
}
}
map(f) {
return new Some(f(this.value));
}
forEach(f) {
f(this.value);
}
toArray() { return [this.value]; }
inspect() { return "Some(" + this.value + ")"; }
toString() { return this.inspect(); }
}
/** @hidden */
const None = {
_isNone: true,
getOrElse(value) { return value; },
get() { throw new Error("None.get()"); },
filter() { return None; },
map() { return None; },
forEach() { },
isDefined: false,
toArray() { return []; },
inspect() { return "None"; },
toString() { return this.inspect(); }
};
function none() { return None; }
function toOption(v) {
if (v && (v._isSome || v._isNone)) {
return v;
}
else {
return new Some(v);
}
}
function isNone(object) {
return ((typeof object !== "undefined" && object !== null) ? object._isNone : false);
}
/** @hidden */
var eventIdCounter = 0;
/**
* Base class for all events passed through [EventStreams](eventstream.html) and [Properties](property.html).
*/
class Event {
constructor() {
this.id = ++eventIdCounter;
/** @hidden */
this.isEvent = true;
/** @hidden */
this._isEvent = true;
this.isEnd = false;
this.isInitial = false;
this.isNext = false;
this.isError = false;
this.hasValue = false;
}
/** @hidden */
filter(f) { return true; }
/** @hidden */
inspect() { return this.toString(); }
/** @hidden */
log() { return this.toString(); }
/** @hidden */
toNext() { return this; }
}
/**
* Base class for all [Events](event.html) carrying a value.
*
* Can be distinguished from other events using [hasValue](../globals.html#hasvalue)
**/
class Value extends Event {
constructor(value) {
super();
this.hasValue = true;
if (value instanceof Event) {
throw new Error$1("Wrapping an event inside other event");
}
this.value = value;
}
/** @hidden */
fmap(f) {
return this.apply(f(this.value));
}
/** @hidden */
filter(f) { return f(this.value); }
/** @hidden */
toString() { return _.toString(this.value); }
//toString(): string { return "<value " + this.id + ">" + _.toString(this.value) }
/** @hidden */
log() { return this.value; }
}
/**
* Indicates a new value in an [EventStream](eventstream.html) or a [Property](property.html).
*
* Can be distinguished from other events using [isNext](../globals.html#isnext)
*/
class Next extends Value {
constructor(value) {
super(value);
this.isNext = true;
/** @hidden */
this._isNext = true; // some compatibility stuff?
}
/** @hidden */
apply(value) { return new Next(value); }
}
/**
* An event carrying the initial value of a [Property](classes/property.html). This event can be emitted by a property
* immediately when subscribing to it.
*
* Can be distinguished from other events using [isInitial](../globals.html#isinitial)
*/
class Initial extends Value {
constructor(value) {
super(value);
this.isInitial = true;
/** @hidden */
this._isInitial = true;
}
/** @hidden */
apply(value) { return new Initial(value); }
/** @hidden */
toNext() { return new Next(this.value); }
}
/**
* Base class for events not carrying a value.
*/
class NoValue extends Event {
constructor() {
super(...arguments);
this.hasValue = false;
}
/** @hidden */
fmap(f) {
return this;
}
}
/**
* An event that indicates the end of an [EventStream](classes/eventstream.html) or a [Property](classes/property.html).
* No more events can be emitted after this one.
*
* Can be distinguished from other events using [isEnd](../globals.html#isend)
*/
class End extends NoValue {
constructor() {
super(...arguments);
this.isEnd = true;
}
/** @hidden */
toString() { return "<end>"; }
}
/**
* An event carrying an error. You can use [onError](observable.html#onerror) to subscribe to errors.
*/
class Error$1 extends NoValue {
constructor(error) {
super();
this.isError = true;
this.error = error;
}
/** @hidden */
toString() {
return "<error> " + _.toString(this.error);
}
}
/** @hidden */
function initialEvent(value) { return new Initial(value); }
/** @hidden */
function nextEvent(value) { return new Next(value); }
/** @hidden */
function endEvent() { return new End(); }
/** @hidden */
function toEvent(x) {
if (x && x._isEvent) {
return x;
}
else {
return nextEvent(x);
}
}
/**
* Returns true if the given object is an [Event](classes/event.html).
*/
function isEvent(e) {
return e && e._isEvent;
}
/**
* Returns true if the given event is an [Initial](classes/initial.html) value of a [Property](classes/property.html).
*/
function isInitial(e) {
return e && e._isInitial;
}
/**
* Returns true if the given event is an [Error](classes/error.html) event of an [Observable](classes/observable.html).
*/
function isError(e) {
return e.isError;
}
/**
* Returns true if the given event is a [Value](classes/value.html), i.e. a [Next](classes/next.html) or
* an [Initial](classes/error.html) value of an [Observable](classes/observable.html).
*/
function hasValue(e) {
return e.hasValue;
}
/**
* Returns true if the given event is an [End](classes/end.html)
*/
function isEnd(e) {
return e.isEnd;
}
/**
* Returns true if the given event is a [Next](classes/next.html)
*/
function isNext(e) {
return e.isNext;
}
/** @hidden */
function equals(a, b) { return a === b; }
/** @hidden */
function skipDuplicates(src, isEqual = equals) {
let desc = new Desc(src, "skipDuplicates", []);
return withStateMachine(none(), function (prev, event) {
if (!hasValue(event)) {
return [prev, [event]];
}
else if (event.isInitial || isNone(prev) || !isEqual(prev.get(), event.value)) {
return [new Some(event.value), [event]];
}
else {
return [prev, []];
}
}, src).withDesc(desc);
}
/** @hidden */
function take(count, src, desc) {
return src.transform(takeT(count), desc || new Desc(src, "take", [count]));
}
/** @hidden */
function takeT(count) {
return (e, sink) => {
if (!e.hasValue) {
return sink(e);
}
else {
count--;
if (count > 0) {
return sink(e);
}
else {
if (count === 0) {
sink(e);
}
sink(endEvent());
return noMore;
}
}
};
}
/** @hidden */
function log(args, src) {
src.subscribe(function (event) {
if (typeof console !== "undefined" && typeof console.log === "function") {
console.log(...args.concat([event.log()]));
}
return more;
});
}
/** @hidden */
function doLogT(args) {
return (event, sink) => {
if (typeof console !== "undefined" && console !== null && typeof console.log === "function") {
console.log(...args.concat([event.log()]));
}
return sink(event);
};
}
/** @hidden */
function doErrorT(f) {
return (event, sink) => {
if (isError(event)) {
f(event.error);
}
return sink(event);
};
}
/** @hidden */
function doActionT(f) {
return (event, sink) => {
if (hasValue(event)) {
f(event.value);
}
return sink(event);
};
}
/** @hidden */
function doEndT(f) {
return (event, sink) => {
if (isEnd(event)) {
f();
}
return sink(event);
};
}
/** @hidden */
function scan(src, seed, f) {
let resultProperty;
let acc = seed;
let initHandled = false;
const subscribe = (sink) => {
var initSent = false;
var unsub = nop;
var reply = more;
const sendInit = function () {
if (!initSent) {
initSent = initHandled = true;
reply = sink(new Initial(acc));
if (reply === noMore) {
unsub();
unsub = nop;
}
}
return reply;
};
unsub = src.subscribeInternal(function (event) {
if (hasValue(event)) {
if (initHandled && event.isInitial) {
//console.log "skip INITIAL"
return more; // init already sent, skip this one
}
else {
if (!event.isInitial) {
sendInit();
}
initSent = initHandled = true;
var prev = acc;
var next = f(prev, event.value);
//console.log prev , ",", event.value, "->", next
acc = next;
return sink(event.apply(next));
}
}
else {
if (event.isEnd) {
reply = sendInit();
}
if (reply !== noMore) {
return sink(event);
}
return reply;
}
});
UpdateBarrier.whenDoneWith(resultProperty, sendInit);
return unsub;
};
return resultProperty = new Property(new Desc(src, "scan", [seed, f]), subscribe);
}
/** @hidden */
function mapEndT(f) {
let theF = _.toFunction(f);
return function (event, sink) {
if (isEnd(event)) {
sink(nextEvent(theF(event)));
sink(endEvent());
return noMore;
}
else {
return sink(event);
}
};
}
/** @hidden */
function mapErrorT(f) {
let theF = _.toFunction(f);
return function (event, sink) {
if (isError(event)) {
return sink(nextEvent(theF(event.error)));
}
else {
return sink(event);
}
};
}
/** @hidden */
function skipErrors(src) {
return src.transform(function (event, sink) {
if (isError(event)) {
return more;
}
else {
return sink(event);
}
}, new Desc(src, "skipErrors", []));
}
/** @hidden */
function last$1(src) {
var lastEvent;
return src.transform(function (event, sink) {
if (isEnd(event)) {
if (lastEvent) {
sink(lastEvent);
}
sink(endEvent());
return noMore;
}
else if (hasValue(event)) {
lastEvent = event;
return more;
}
else {
return sink(event);
}
}).withDesc(new Desc(src, "last", []));
}
/** @hidden */
class CompositeUnsubscribe {
constructor(ss = []) {
this.unsubscribed = false;
this.unsubscribe = _.bind(this.unsubscribe, this);
this.unsubscribed = false;
this.subscriptions = [];
this.starting = [];
for (var i = 0, s; i < ss.length; i++) {
s = ss[i];
this.add(s);
}
}
add(subscription) {
if (!this.unsubscribed) {
var ended = false;
var unsub = nop;
this.starting.push(subscription);
var unsubMe = () => {
if (this.unsubscribed) {
return;
}
ended = true;
this.remove(unsub);
_.remove(subscription, this.starting);
};
unsub = subscription(this.unsubscribe, unsubMe);
if (!(this.unsubscribed || ended)) {
this.subscriptions.push(unsub);
}
else {
unsub();
}
_.remove(subscription, this.starting);
}
}
remove(unsub) {
if (this.unsubscribed) {
return;
}
if ((_.remove(unsub, this.subscriptions)) !== undefined) {
return unsub();
}
}
unsubscribe() {
if (this.unsubscribed) {
return;
}
this.unsubscribed = true;
var iterable = this.subscriptions;
for (var i = 0; i < iterable.length; i++) {
iterable[i]();
}
this.subscriptions = [];
this.starting = [];
}
count() {
if (this.unsubscribed) {
return 0;
}
return this.subscriptions.length + this.starting.length;
}
empty() {
return this.count() === 0;
}
}
/** @hidden */
function streamSubscribeToPropertySubscribe(initValue, streamSubscribe) {
//assertFunction(streamSubscribe)
return function (sink) {
var initSent = false;
var subbed = false;
var unsub = nop;
var reply = more;
var sendInit = function () {
if (!initSent) {
return initValue.forEach(function (value) {
initSent = true;
reply = sink(new Initial(value));
if (reply === noMore) {
unsub();
unsub = nop;
return nop;
}
});
}
};
unsub = streamSubscribe(function (event) {
if (event instanceof Value) {
if (event.isInitial && !subbed) {
initValue = new Some(event.value);
return more;
}
else {
if (!event.isInitial) {
sendInit();
}
initSent = true;
initValue = new Some(event.value);
return sink(event);
}
}
else {
if (event.isEnd) {
reply = sendInit();
}
if (reply !== noMore) {
return sink(event);
}
return reply;
}
});
subbed = true;
sendInit();
return unsub;
};
}
/** @hidden */
function propertyFromStreamSubscribe(desc, subscribe) {
assertFunction(subscribe);
return new Property(desc, streamSubscribeToPropertySubscribe(none(), subscribe));
}
/**
Creates an EventStream that delivers the given
single value for the first subscriber. The stream will end immediately
after this value. You can also send an [`Bacon.Error`](#bacon-error) event instead of a
value: `Bacon.once(new Bacon.Error("fail"))`.
@param value the value or event to emit
@typeparam V Type of stream elements
*/
function once(value) {
const s = new EventStream(new Desc("Bacon", "once", [value]), function (sink) {
UpdateBarrier.soonButNotYet(s, function () {
sink(toEvent(value));
sink(endEvent());
});
return nop;
});
return s;
}
/** @hidden */
function flatMap_(spawner, src, params = {}) {
const root = src;
const rootDep = [root];
const childDeps = [];
const isProperty = src._isProperty;
const ctor = (isProperty ? propertyFromStreamSubscribe : newEventStreamAllowSync);
let initialSpawned = false;
const desc = params.desc || new Desc(src, "flatMap_", [spawner]);
const result = ctor(desc, function (sink) {
const composite = new CompositeUnsubscribe();
const queue = [];
function spawn(event) {
if (isProperty && event.isInitial) {
if (initialSpawned) {
return more;
}
initialSpawned = true;
}
const child = makeObservable(spawner(event));
childDeps.push(child);
return composite.add(function (unsubAll, unsubMe) {
return child.subscribeInternal(function (event) {
if (event.isEnd) {
_.remove(child, childDeps);
checkQueue();
checkEnd(unsubMe);
return noMore;
}
else {
event = event.toNext(); // To support Property as the spawned stream
const reply = sink(event);
if (reply === noMore) {
unsubAll();
}
return reply;
}
});
});
}
function checkQueue() {
const event = queue.shift();
if (event) {
spawn(event);
}
}
function checkEnd(unsub) {
unsub();
if (composite.empty()) {
return sink(endEvent());
}
return more;
}
composite.add(function (__, unsubRoot) {
return root.subscribeInternal(function (event) {
if (event.isEnd) {
return checkEnd(unsubRoot);
}
else if (event.isError && !params.mapError) {
return sink(event);
}
else if (params.firstOnly && composite.count() > 1) {
return more;
}
else {
if (composite.unsubscribed) {
return noMore;
}
if (params.limit && composite.count() > params.limit) {
queue.push(event);
}
else {
spawn(event);
}
return more;
}
});
});
return composite.unsubscribe;
});
result.internalDeps = function () {
if (childDeps.length) {
return rootDep.concat(childDeps);
}
else {
return rootDep;
}
};
return result;
}
/** @hidden */
function handleEventValueWith(f) {
if (typeof f == "function") {
return ((event) => {
if (hasValue(event)) {
return f(event.value);
}
return event;
});
}
return ((event) => f);
}
/** @hidden */
function makeObservable(x) {
if (isObservable(x)) {
return x;
}
else {
return once(x);
}
}
/** @hidden */
function flatMapEvent(src, f) {
return flatMap_(f, src, {
mapError: true,
desc: new Desc(src, "flatMapEvent", [f])
});
}
/** @hidden */
function endAsValue(src) {
return src.transform((event, sink) => {
if (isEnd(event)) {
sink(nextEvent({}));
sink(endEvent());
return noMore;
}
return more;
});
}
/** @hidden */
function endOnError(src, predicate = x => true) {
return src.transform((event, sink) => {
if (isError(event) && predicate(event.error)) {
sink(event);
return sink(endEvent());
}
else {
return sink(event);
}
}, new Desc(src, "endOnError", []));
}
/** @hidden */
class Source {
constructor(obs, sync) {
this._isSource = true;
this.flatten = true;
this.ended = false;
this.obs = obs;
this.sync = sync;
}
subscribe(sink) {
return this.obs.subscribeInternal(sink);
}
toString() {
return this.obs.toString();
}
markEnded() {
this.ended = true;
}
mayHave(count) { return true; }
}
/** @hidden */
class DefaultSource extends Source {
consume() {
return this.value;
}
push(x) {
this.value = x;
}
hasAtLeast(c) {
return !!this.value;
}
}
/** @hidden */
class ConsumingSource extends Source {
constructor(obs, sync) {
super(obs, sync);
this.flatten = false;
this.queue = [];
}
consume() {
return this.queue.shift();
}
push(x) {
this.queue.push(x);
}
mayHave(count) {
return !this.ended || this.queue.length >= count;
}
hasAtLeast(count) {
return this.queue.length >= count;
}
}
/** @hidden */
class BufferingSource extends Source {
constructor(obs) {
super(obs, true);
this.queue = [];
}
consume() {
const values = this.queue;
this.queue = [];
return {
value: values
};
}
push(x) {
return this.queue.push(x.value);
}
hasAtLeast(count) {
return true;
}
}
/** @hidden */
function isTrigger(s) {
if (s == null)
return false;
if (s._isSource) {
return s.sync;
}
else {
return s._isEventStream;
}
}
/** @hidden */
function fromObservable(s) {
if (s != null && s._isSource) {
return s;
}
else if (s != null && s._isProperty) {
return new DefaultSource(s, false);
}
else {
return new ConsumingSource(s, true);
}
}
/**
Creates an EventStream that immediately ends.
@typeparam V Type of stream elements
*/
function never() {
return new EventStream(describe("Bacon", "never"), (sink) => {
sink(endEvent());
return nop;
});
}
/**
The `when` method provides a generalization of the [`zip`](classes/observable.html#zip) function. While zip
synchronizes events from multiple streams pairwse, the join patterns used in `when` allow
the implementation of more advanced synchronization patterns.
Consider implementing a game with discrete time ticks. We want to
handle key-events synchronized on tick-events, with at most one key
event handled per tick. If there are no key events, we want to just
process a tick.
```js
Bacon.when(
[tick, keyEvent, function(_, k) { handleKeyEvent(k); return handleTick(); }],
[tick, handleTick])
```
Order is important here. If the [tick] patterns had been written
first, this would have been tried first, and preferred at each tick.
Join patterns are indeed a generalization of zip, and for EventStreams, zip is
equivalent to a single-rule join pattern. The following observables
have the same output, assuming that all sources are EventStreams.
```js
Bacon.zipWith(a,b,c, combine)
Bacon.when([a,b,c], combine)
```
Note that [`Bacon.when`](#bacon-when) does not trigger updates for events from Properties though;
if you use a Property in your pattern, its value will be just sampled when all the
other sources (EventStreams) have a value. This is useful when you need a value of a Property
in your calculations. If you want your pattern to fire for a Property too, you can
convert it into an EventStream using [`property.changes()`](#property-changes) or [`property.toEventStream()`](#property-toeventstream)
* @param {Pattern<O>} patterns Join patterns
* @typeparam O result type
*/
function when(...patterns) {
return when_(newEventStream, patterns);
}
/** @hidden */
function whenP(...patterns) {
return when_(propertyFromStreamSubscribe, patterns);
}
/** @hidden */
function when_(ctor, patterns) {
if (patterns.length === 0) {
return never();
}
var [sources, ixPats] = processRawPatterns(extractRawPatterns(patterns));
if (!sources.length) {
return never();
}
var needsBarrier = (any(sources, (s) => s.flatten)) && containsDuplicateDeps(map(((s) => s.obs), sources));
var desc = new Desc("Bacon", "when", Array.prototype.slice.call(patterns));
var resultStream = ctor(desc, function (sink) {
var triggers = [];
var ends = false;
function match(p) {
for (var i = 0; i < p.ixs.length; i++) {
let ix = p.ixs[i];
if (!sources[ix.index].hasAtLeast(ix.count)) {
return false;
}
}
return true;
}
function cannotMatch(p) {
for (var i = 0; i < p.ixs.length; i++) {
let ix = p.ixs[i];
if (!sources[ix.index].mayHave(ix.count)) {
return true;
}
}
return false;
}
function nonFlattened(trigger) { return !trigger.source.flatten; }
function part(source) {
return function (unsubAll) {
function flushLater() {
return UpdateBarrier.whenDoneWith(resultStream, flush);
}
function flushWhileTriggers() {
var trigger;
if ((trigger = triggers.pop()) !== undefined) {
var reply = more;
for (var i = 0, p; i < ixPats.length; i++) {
p = ixPats[i];
if (match(p)) {
const values = [];
for (var j = 0; j < p.ixs.length; j++) {
let event = sources[p.ixs[j].index].consume();
if (!event)
throw new Error("Event was undefined");
values.push(event.value);
}
//console.log("flushing values", values)
let applied = p.f.apply(null, values);
//console.log('sinking', applied)
reply = sink((trigger).e.apply(applied));
if (triggers.length) {
triggers = filter(nonFlattened, triggers);
}
if (reply === noMore) {
return reply;
}
else {
return flushWhileTriggers();
}
}
}
}
return more;
}
function flush() {
//console.log "flushing", _.toString(resultStream)
var reply = flushWhileTriggers();
if (ends) {
//console.log "ends detected"
if (all(sources, cannotSync) || all(ixPats, cannotMatch)) {
//console.log "actually ending"
reply = noMore;
sink(endEvent());
}
}
if (reply === noMore) {
unsubAll();
}
}
return source.subscribe(function (e) {
var reply = more;
if (e.isEnd) {
//console.log "got end"
ends = true;
source.markEnded();
flushLater();
}
else if (e.isError) {
reply = sink(e);
}
else {
let valueEvent = e;
//console.log "got value", e.value
source.push(valueEvent);
if (source.sync) {
//console.log "queuing", e.toString(), toString(resultStream)
triggers.push({ source: source, e: valueEvent });
if (needsBarrier || UpdateBarrier.hasWaiters()) {
flushLater();
}
else {
flush();
}
}
}
if (reply === noMore) {
unsubAll();
}
return reply;
});
};
}
return new CompositeUnsubscribe(map(part, sources)).unsubscribe;
});
return resultStream;
}
function processRawPatterns(rawPatterns) {
var sources = [];
var pats = [];
for (let i = 0; i < rawPatterns.length; i++) {
let [patSources, f] = rawPatterns[i];
var pat = { f, ixs: [] };
var triggerFound = false;
for (var j = 0, s; j < patSources.length; j++) {
s = patSources[j];
var index = indexOf(sources, s);
if (!triggerFound) {
triggerFound = isTrigger(s);
}
if (index < 0) {
sources.push(s);
index = sources.length - 1;
}
for (var k = 0; k < pat.ixs.length; k++) {
let ix = pat.ixs[k];
if (ix.index === index) {
ix.count++;
}
}
pat.ixs.push({ index: index, count: 1 });
}
if (patSources.length > 0 && !triggerFound) {
throw new Error("At least one EventStream required, none found in " + patSources);
}
if (patSources.length > 0) {
pats.push(pat);
}
}
return [map(fromObservable /* sorry */, sources), pats];
}
function extractLegacyPatterns(sourceArgs) {
var i = 0;
var len = sourceArgs.length;
var rawPatterns = [];
while (i < len) {
let patSources = toArray(sourceArgs[i++]);
let f = toFunction(sourceArgs[i++]);
rawPatterns.push([patSources, f]);
}
var usage = "when: expecting arguments in the form (Observable+,function)+";
assert(usage, (len % 2 === 0));
return rawPatterns;
}
function isTypedOrRawPattern(pattern) {
return (pattern instanceof Array) && (!isObservable(pattern[pattern.length - 1]));
}
function isRawPattern(pattern) {
return pattern[0] instanceof Array;
}
/** @hidden */
function extractRawPatterns(patterns) {
let rawPatterns = [];
for (let i = 0; i < patterns.length; i++) {
let pattern = patterns[i];
if (!isTypedOrRawPattern(pattern)) {
// Fallback to legacy patterns
return extractLegacyPatterns(patterns);
}
if (isRawPattern(pattern)) {
rawPatterns.push([pattern[0], toFunction(pattern[1])]);
}
else { // typed pattern, then
let sources = pattern.slice(0, pattern.length - 1);
let f = toFunction(pattern[pattern.length - 1]);
rawPatterns.push([sources, f]);
}
}
return rawPatterns;
}
function containsDuplicateDeps(observables, state = []) {
function checkObservable(obs) {
if (contains(state, obs)) {
return true;
}
else {
var deps = obs.internalDeps();
if (deps.length) {
state.push(obs);
return any(deps, checkObservable);
}
else {
state.push(obs);
return false;
}
}
}
return any(observables, checkObservable);
}
function cannotSync(source) {
return !source.sync || source.ended;
}
/** @hidden */
function withLatestFromE(sampler, samplee, f) {
var result = when([new DefaultSource(samplee.toProperty(), false), new DefaultSource(sampler, true), flip(f)]);
return result.withDesc(new Desc(sampler, "withLatestFrom", [samplee, f]));
}
/** @hidden */
function withLatestFromP(sampler, samplee, f) {
var result = whenP([new DefaultSource(samplee.toProperty(), false), new DefaultSource(sampler, true), flip(f)]);
return result.withDesc(new Desc(sampler, "withLatestFrom", [samplee, f]));
}
/** @hidden */
function withLatestFrom(sampler, samplee, f) {
if (sampler instanceof Property) {
return withLatestFromP(sampler, samplee, f);
}
else if (sampler instanceof EventStream) {
return withLatestFromE(sampler, samplee, f);
}
else {
throw new Error("Unknown observable: " + sampler);
}
}
/** @hidden */
function map$1(src, f) {
if (f instanceof Property) {
return withLatestFrom(src, f, (a, b) => b);
}
return src.transform(mapT(f), new Desc(src, "map", [f]));
}
/** @hidden */
function mapT(f) {
let theF = _.toFunction(f);
return (e, sink) => {
return sink(e.fmap(theF));
};
}
/**
Creates a constant property with value `x`.
*/
function constant(x) {
return new Property(new Desc("Bacon", "constant", [x]), function (sink) {
sink(initialEvent(x));
sink(endEvent());
return nop;
});
}
/** @hidden */
function argumentsToObservables(args) {
args = (Array.prototype.slice.call(args));
return _.flatMap(singleToObservables, args);
}
function singleToObservables(x) {
if (isObservable(x)) {
return [x];
}
else if (isArray(x)) {
return argumentsToObservables(x);
}
else {
return [constant(x)];
}
}
/** @hidden */
function argumentsToObservablesAndFunction(args) {
if (_.isFunction(args[0])) {
return [argumentsToObservables(Array.prototype.slice.call(args, 1)), args[0]];
}
else {
return [argumentsToObservables(Array.prototype.slice.call(args, 0, args.length - 1)), _.last(args)];
}
}
/** @hidden */
function groupSimultaneous(...streams) {
return groupSimultaneous_(argumentsToObservables(streams));
}
// TODO: type is not exactly correct, because different inputs may have different types.
// Result values are arrays where each element is the list from each input observable. Type this.
/** @hidden */
function groupSimultaneous_(streams, options) {
let sources = _.map((stream) => new BufferingSource(stream), streams);
let ctor = (desc, subscribe) => new EventStream(desc, subscribe, undefined, options);
return when_(ctor, [sources, (function (...xs) {
return xs;
})]).withDesc(new Desc("Bacon", "groupSimultaneous", streams));
}
/** @hidden */
function awaiting(src, other) {
return groupSimultaneous_([src, other], allowSync)
.map((values) => values[1].length === 0)
.toProperty(false)
.skipDuplicates()
.withDesc(new Desc(src, "awaiting", [other]));
}
/**
Combines Properties, EventStreams and constant values so that the result Property will have an array of the latest
values from all sources as its value. The inputs may contain both Properties and EventStreams.
```js
property = Bacon.constant(1)
stream = Bacon.once(2)
constant = 3
Bacon.combineAsArray(property, stream, constant)
# produces the value [1,2,3]
```
* @param streams streams and properties to combine
*/
function combineAsArray(...streams) {
streams = argumentsToObservables(streams);
if (streams.length) {
var sources = [];
for (var i = 0; i < streams.length; i++) {
let stream = (isObservable(streams[i])
? streams[i]
: constant(streams[i]));
sources.push(wrap(stream));
}
return whenP([sources, (...xs) => xs]).with