570 lines
17 KiB
JavaScript
570 lines
17 KiB
JavaScript
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||
|
|
||
|
;(function (factory) {
|
||
|
var objectTypes = {
|
||
|
'boolean': false,
|
||
|
'function': true,
|
||
|
'object': true,
|
||
|
'number': false,
|
||
|
'string': false,
|
||
|
'undefined': false
|
||
|
};
|
||
|
|
||
|
var root = (objectTypes[typeof window] && window) || this,
|
||
|
freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports,
|
||
|
freeModule = objectTypes[typeof module] && module && !module.nodeType && module,
|
||
|
moduleExports = freeModule && freeModule.exports === freeExports && freeExports,
|
||
|
freeGlobal = objectTypes[typeof global] && global;
|
||
|
|
||
|
if (freeGlobal && (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal)) {
|
||
|
root = freeGlobal;
|
||
|
}
|
||
|
|
||
|
// Because of build optimizers
|
||
|
if (typeof define === 'function' && define.amd) {
|
||
|
define(['rx'], function (Rx, exports) {
|
||
|
return factory(root, exports, Rx);
|
||
|
});
|
||
|
} else if (typeof module === 'object' && module && module.exports === freeExports) {
|
||
|
module.exports = factory(root, module.exports, require('./rx'));
|
||
|
} else {
|
||
|
root.Rx = factory(root, {}, root.Rx);
|
||
|
}
|
||
|
}.call(this, function (root, exp, Rx, undefined) {
|
||
|
|
||
|
// References
|
||
|
var Observable = Rx.Observable,
|
||
|
observableProto = Observable.prototype,
|
||
|
AnonymousObservable = Rx.AnonymousObservable,
|
||
|
AbstractObserver = Rx.internals.AbstractObserver,
|
||
|
CompositeDisposable = Rx.CompositeDisposable,
|
||
|
Subject = Rx.Subject,
|
||
|
Observer = Rx.Observer,
|
||
|
disposableEmpty = Rx.Disposable.empty,
|
||
|
disposableCreate = Rx.Disposable.create,
|
||
|
inherits = Rx.internals.inherits,
|
||
|
addProperties = Rx.internals.addProperties,
|
||
|
timeoutScheduler = Rx.Scheduler.timeout,
|
||
|
currentThreadScheduler = Rx.Scheduler.currentThread,
|
||
|
identity = Rx.helpers.identity;
|
||
|
|
||
|
var objectDisposed = 'Object has been disposed';
|
||
|
function checkDisposed() { if (this.isDisposed) { throw new Error(objectDisposed); } }
|
||
|
|
||
|
/**
|
||
|
* Used to pause and resume streams.
|
||
|
*/
|
||
|
Rx.Pauser = (function (__super__) {
|
||
|
inherits(Pauser, __super__);
|
||
|
|
||
|
function Pauser() {
|
||
|
__super__.call(this);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Pauses the underlying sequence.
|
||
|
*/
|
||
|
Pauser.prototype.pause = function () { this.onNext(false); };
|
||
|
|
||
|
/**
|
||
|
* Resumes the underlying sequence.
|
||
|
*/
|
||
|
Pauser.prototype.resume = function () { this.onNext(true); };
|
||
|
|
||
|
return Pauser;
|
||
|
}(Subject));
|
||
|
|
||
|
var PausableObservable = (function (__super__) {
|
||
|
|
||
|
inherits(PausableObservable, __super__);
|
||
|
|
||
|
function subscribe(observer) {
|
||
|
var conn = this.source.publish(),
|
||
|
subscription = conn.subscribe(observer),
|
||
|
connection = disposableEmpty;
|
||
|
|
||
|
var pausable = this.pauser.distinctUntilChanged().subscribe(function (b) {
|
||
|
if (b) {
|
||
|
connection = conn.connect();
|
||
|
} else {
|
||
|
connection.dispose();
|
||
|
connection = disposableEmpty;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
return new CompositeDisposable(subscription, connection, pausable);
|
||
|
}
|
||
|
|
||
|
function PausableObservable(source, pauser) {
|
||
|
this.source = source;
|
||
|
this.controller = new Subject();
|
||
|
|
||
|
if (pauser && pauser.subscribe) {
|
||
|
this.pauser = this.controller.merge(pauser);
|
||
|
} else {
|
||
|
this.pauser = this.controller;
|
||
|
}
|
||
|
|
||
|
__super__.call(this, subscribe, source);
|
||
|
}
|
||
|
|
||
|
PausableObservable.prototype.pause = function () {
|
||
|
this.controller.onNext(false);
|
||
|
};
|
||
|
|
||
|
PausableObservable.prototype.resume = function () {
|
||
|
this.controller.onNext(true);
|
||
|
};
|
||
|
|
||
|
return PausableObservable;
|
||
|
|
||
|
}(Observable));
|
||
|
|
||
|
/**
|
||
|
* Pauses the underlying observable sequence based upon the observable sequence which yields true/false.
|
||
|
* @example
|
||
|
* var pauser = new Rx.Subject();
|
||
|
* var source = Rx.Observable.interval(100).pausable(pauser);
|
||
|
* @param {Observable} pauser The observable sequence used to pause the underlying sequence.
|
||
|
* @returns {Observable} The observable sequence which is paused based upon the pauser.
|
||
|
*/
|
||
|
observableProto.pausable = function (pauser) {
|
||
|
return new PausableObservable(this, pauser);
|
||
|
};
|
||
|
|
||
|
function combineLatestSource(source, subject, resultSelector) {
|
||
|
return new AnonymousObservable(function (o) {
|
||
|
var hasValue = [false, false],
|
||
|
hasValueAll = false,
|
||
|
isDone = false,
|
||
|
values = new Array(2),
|
||
|
err;
|
||
|
|
||
|
function next(x, i) {
|
||
|
values[i] = x
|
||
|
var res;
|
||
|
hasValue[i] = true;
|
||
|
if (hasValueAll || (hasValueAll = hasValue.every(identity))) {
|
||
|
if (err) {
|
||
|
o.onError(err);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
res = resultSelector.apply(null, values);
|
||
|
} catch (ex) {
|
||
|
o.onError(ex);
|
||
|
return;
|
||
|
}
|
||
|
o.onNext(res);
|
||
|
}
|
||
|
if (isDone && values[1]) {
|
||
|
o.onCompleted();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return new CompositeDisposable(
|
||
|
source.subscribe(
|
||
|
function (x) {
|
||
|
next(x, 0);
|
||
|
},
|
||
|
function (e) {
|
||
|
if (values[1]) {
|
||
|
o.onError(e);
|
||
|
} else {
|
||
|
err = e;
|
||
|
}
|
||
|
},
|
||
|
function () {
|
||
|
isDone = true;
|
||
|
values[1] && o.onCompleted();
|
||
|
}),
|
||
|
subject.subscribe(
|
||
|
function (x) {
|
||
|
next(x, 1);
|
||
|
},
|
||
|
function (e) { o.onError(e); },
|
||
|
function () {
|
||
|
isDone = true;
|
||
|
next(true, 1);
|
||
|
})
|
||
|
);
|
||
|
}, source);
|
||
|
}
|
||
|
|
||
|
var PausableBufferedObservable = (function (__super__) {
|
||
|
|
||
|
inherits(PausableBufferedObservable, __super__);
|
||
|
|
||
|
function subscribe(o) {
|
||
|
var q = [], previousShouldFire;
|
||
|
|
||
|
var subscription =
|
||
|
combineLatestSource(
|
||
|
this.source,
|
||
|
this.pauser.distinctUntilChanged().startWith(false),
|
||
|
function (data, shouldFire) {
|
||
|
return { data: data, shouldFire: shouldFire };
|
||
|
})
|
||
|
.subscribe(
|
||
|
function (results) {
|
||
|
if (previousShouldFire !== undefined && results.shouldFire != previousShouldFire) {
|
||
|
previousShouldFire = results.shouldFire;
|
||
|
// change in shouldFire
|
||
|
if (results.shouldFire) {
|
||
|
while (q.length > 0) {
|
||
|
o.onNext(q.shift());
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
previousShouldFire = results.shouldFire;
|
||
|
// new data
|
||
|
if (results.shouldFire) {
|
||
|
o.onNext(results.data);
|
||
|
} else {
|
||
|
q.push(results.data);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
function (err) {
|
||
|
// Empty buffer before sending error
|
||
|
while (q.length > 0) {
|
||
|
o.onNext(q.shift());
|
||
|
}
|
||
|
o.onError(err);
|
||
|
},
|
||
|
function () {
|
||
|
// Empty buffer before sending completion
|
||
|
while (q.length > 0) {
|
||
|
o.onNext(q.shift());
|
||
|
}
|
||
|
o.onCompleted();
|
||
|
}
|
||
|
);
|
||
|
return subscription;
|
||
|
}
|
||
|
|
||
|
function PausableBufferedObservable(source, pauser) {
|
||
|
this.source = source;
|
||
|
this.controller = new Subject();
|
||
|
|
||
|
if (pauser && pauser.subscribe) {
|
||
|
this.pauser = this.controller.merge(pauser);
|
||
|
} else {
|
||
|
this.pauser = this.controller;
|
||
|
}
|
||
|
|
||
|
__super__.call(this, subscribe, source);
|
||
|
}
|
||
|
|
||
|
PausableBufferedObservable.prototype.pause = function () {
|
||
|
this.controller.onNext(false);
|
||
|
};
|
||
|
|
||
|
PausableBufferedObservable.prototype.resume = function () {
|
||
|
this.controller.onNext(true);
|
||
|
};
|
||
|
|
||
|
return PausableBufferedObservable;
|
||
|
|
||
|
}(Observable));
|
||
|
|
||
|
/**
|
||
|
* Pauses the underlying observable sequence based upon the observable sequence which yields true/false,
|
||
|
* and yields the values that were buffered while paused.
|
||
|
* @example
|
||
|
* var pauser = new Rx.Subject();
|
||
|
* var source = Rx.Observable.interval(100).pausableBuffered(pauser);
|
||
|
* @param {Observable} pauser The observable sequence used to pause the underlying sequence.
|
||
|
* @returns {Observable} The observable sequence which is paused based upon the pauser.
|
||
|
*/
|
||
|
observableProto.pausableBuffered = function (subject) {
|
||
|
return new PausableBufferedObservable(this, subject);
|
||
|
};
|
||
|
|
||
|
var ControlledObservable = (function (__super__) {
|
||
|
|
||
|
inherits(ControlledObservable, __super__);
|
||
|
|
||
|
function subscribe (observer) {
|
||
|
return this.source.subscribe(observer);
|
||
|
}
|
||
|
|
||
|
function ControlledObservable (source, enableQueue) {
|
||
|
__super__.call(this, subscribe, source);
|
||
|
this.subject = new ControlledSubject(enableQueue);
|
||
|
this.source = source.multicast(this.subject).refCount();
|
||
|
}
|
||
|
|
||
|
ControlledObservable.prototype.request = function (numberOfItems) {
|
||
|
if (numberOfItems == null) { numberOfItems = -1; }
|
||
|
return this.subject.request(numberOfItems);
|
||
|
};
|
||
|
|
||
|
return ControlledObservable;
|
||
|
|
||
|
}(Observable));
|
||
|
|
||
|
var ControlledSubject = (function (__super__) {
|
||
|
|
||
|
function subscribe (observer) {
|
||
|
return this.subject.subscribe(observer);
|
||
|
}
|
||
|
|
||
|
inherits(ControlledSubject, __super__);
|
||
|
|
||
|
function ControlledSubject(enableQueue) {
|
||
|
enableQueue == null && (enableQueue = true);
|
||
|
|
||
|
__super__.call(this, subscribe);
|
||
|
this.subject = new Subject();
|
||
|
this.enableQueue = enableQueue;
|
||
|
this.queue = enableQueue ? [] : null;
|
||
|
this.requestedCount = 0;
|
||
|
this.requestedDisposable = disposableEmpty;
|
||
|
this.error = null;
|
||
|
this.hasFailed = false;
|
||
|
this.hasCompleted = false;
|
||
|
this.controlledDisposable = disposableEmpty;
|
||
|
}
|
||
|
|
||
|
addProperties(ControlledSubject.prototype, Observer, {
|
||
|
onCompleted: function () {
|
||
|
this.hasCompleted = true;
|
||
|
(!this.enableQueue || this.queue.length === 0) && this.subject.onCompleted();
|
||
|
},
|
||
|
onError: function (error) {
|
||
|
this.hasFailed = true;
|
||
|
this.error = error;
|
||
|
(!this.enableQueue || this.queue.length === 0) && this.subject.onError(error);
|
||
|
},
|
||
|
onNext: function (value) {
|
||
|
var hasRequested = false;
|
||
|
|
||
|
if (this.requestedCount === 0) {
|
||
|
this.enableQueue && this.queue.push(value);
|
||
|
} else {
|
||
|
(this.requestedCount !== -1 && this.requestedCount-- === 0) && this.disposeCurrentRequest();
|
||
|
hasRequested = true;
|
||
|
}
|
||
|
hasRequested && this.subject.onNext(value);
|
||
|
},
|
||
|
_processRequest: function (numberOfItems) {
|
||
|
if (this.enableQueue) {
|
||
|
while (this.queue.length >= numberOfItems && numberOfItems > 0) {
|
||
|
this.subject.onNext(this.queue.shift());
|
||
|
numberOfItems--;
|
||
|
}
|
||
|
|
||
|
return this.queue.length !== 0 ?
|
||
|
{ numberOfItems: numberOfItems, returnValue: true } :
|
||
|
{ numberOfItems: numberOfItems, returnValue: false };
|
||
|
}
|
||
|
|
||
|
if (this.hasFailed) {
|
||
|
this.subject.onError(this.error);
|
||
|
this.controlledDisposable.dispose();
|
||
|
this.controlledDisposable = disposableEmpty;
|
||
|
} else if (this.hasCompleted) {
|
||
|
this.subject.onCompleted();
|
||
|
this.controlledDisposable.dispose();
|
||
|
this.controlledDisposable = disposableEmpty;
|
||
|
}
|
||
|
|
||
|
return { numberOfItems: numberOfItems, returnValue: false };
|
||
|
},
|
||
|
request: function (number) {
|
||
|
this.disposeCurrentRequest();
|
||
|
var self = this, r = this._processRequest(number);
|
||
|
|
||
|
var number = r.numberOfItems;
|
||
|
if (!r.returnValue) {
|
||
|
this.requestedCount = number;
|
||
|
this.requestedDisposable = disposableCreate(function () {
|
||
|
self.requestedCount = 0;
|
||
|
});
|
||
|
|
||
|
return this.requestedDisposable
|
||
|
} else {
|
||
|
return disposableEmpty;
|
||
|
}
|
||
|
},
|
||
|
disposeCurrentRequest: function () {
|
||
|
this.requestedDisposable.dispose();
|
||
|
this.requestedDisposable = disposableEmpty;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
return ControlledSubject;
|
||
|
}(Observable));
|
||
|
|
||
|
/**
|
||
|
* Attaches a controller to the observable sequence with the ability to queue.
|
||
|
* @example
|
||
|
* var source = Rx.Observable.interval(100).controlled();
|
||
|
* source.request(3); // Reads 3 values
|
||
|
* @param {Observable} pauser The observable sequence used to pause the underlying sequence.
|
||
|
* @returns {Observable} The observable sequence which is paused based upon the pauser.
|
||
|
*/
|
||
|
observableProto.controlled = function (enableQueue) {
|
||
|
if (enableQueue == null) { enableQueue = true; }
|
||
|
return new ControlledObservable(this, enableQueue);
|
||
|
};
|
||
|
|
||
|
var StopAndWaitObservable = (function (__super__) {
|
||
|
|
||
|
function subscribe (observer) {
|
||
|
this.subscription = this.source.subscribe(new StopAndWaitObserver(observer, this, this.subscription));
|
||
|
|
||
|
var self = this;
|
||
|
timeoutScheduler.schedule(function () { self.source.request(1); });
|
||
|
|
||
|
return this.subscription;
|
||
|
}
|
||
|
|
||
|
inherits(StopAndWaitObservable, __super__);
|
||
|
|
||
|
function StopAndWaitObservable (source) {
|
||
|
__super__.call(this, subscribe, source);
|
||
|
this.source = source;
|
||
|
}
|
||
|
|
||
|
var StopAndWaitObserver = (function (__sub__) {
|
||
|
|
||
|
inherits(StopAndWaitObserver, __sub__);
|
||
|
|
||
|
function StopAndWaitObserver (observer, observable, cancel) {
|
||
|
__sub__.call(this);
|
||
|
this.observer = observer;
|
||
|
this.observable = observable;
|
||
|
this.cancel = cancel;
|
||
|
}
|
||
|
|
||
|
var stopAndWaitObserverProto = StopAndWaitObserver.prototype;
|
||
|
|
||
|
stopAndWaitObserverProto.completed = function () {
|
||
|
this.observer.onCompleted();
|
||
|
this.dispose();
|
||
|
};
|
||
|
|
||
|
stopAndWaitObserverProto.error = function (error) {
|
||
|
this.observer.onError(error);
|
||
|
this.dispose();
|
||
|
}
|
||
|
|
||
|
stopAndWaitObserverProto.next = function (value) {
|
||
|
this.observer.onNext(value);
|
||
|
|
||
|
var self = this;
|
||
|
timeoutScheduler.schedule(function () {
|
||
|
self.observable.source.request(1);
|
||
|
});
|
||
|
};
|
||
|
|
||
|
stopAndWaitObserverProto.dispose = function () {
|
||
|
this.observer = null;
|
||
|
if (this.cancel) {
|
||
|
this.cancel.dispose();
|
||
|
this.cancel = null;
|
||
|
}
|
||
|
__sub__.prototype.dispose.call(this);
|
||
|
};
|
||
|
|
||
|
return StopAndWaitObserver;
|
||
|
}(AbstractObserver));
|
||
|
|
||
|
return StopAndWaitObservable;
|
||
|
}(Observable));
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Attaches a stop and wait observable to the current observable.
|
||
|
* @returns {Observable} A stop and wait observable.
|
||
|
*/
|
||
|
ControlledObservable.prototype.stopAndWait = function () {
|
||
|
return new StopAndWaitObservable(this);
|
||
|
};
|
||
|
|
||
|
var WindowedObservable = (function (__super__) {
|
||
|
|
||
|
function subscribe (observer) {
|
||
|
this.subscription = this.source.subscribe(new WindowedObserver(observer, this, this.subscription));
|
||
|
|
||
|
var self = this;
|
||
|
timeoutScheduler.schedule(function () {
|
||
|
self.source.request(self.windowSize);
|
||
|
});
|
||
|
|
||
|
return this.subscription;
|
||
|
}
|
||
|
|
||
|
inherits(WindowedObservable, __super__);
|
||
|
|
||
|
function WindowedObservable(source, windowSize) {
|
||
|
__super__.call(this, subscribe, source);
|
||
|
this.source = source;
|
||
|
this.windowSize = windowSize;
|
||
|
}
|
||
|
|
||
|
var WindowedObserver = (function (__sub__) {
|
||
|
|
||
|
inherits(WindowedObserver, __sub__);
|
||
|
|
||
|
function WindowedObserver(observer, observable, cancel) {
|
||
|
this.observer = observer;
|
||
|
this.observable = observable;
|
||
|
this.cancel = cancel;
|
||
|
this.received = 0;
|
||
|
}
|
||
|
|
||
|
var windowedObserverPrototype = WindowedObserver.prototype;
|
||
|
|
||
|
windowedObserverPrototype.completed = function () {
|
||
|
this.observer.onCompleted();
|
||
|
this.dispose();
|
||
|
};
|
||
|
|
||
|
windowedObserverPrototype.error = function (error) {
|
||
|
this.observer.onError(error);
|
||
|
this.dispose();
|
||
|
};
|
||
|
|
||
|
windowedObserverPrototype.next = function (value) {
|
||
|
this.observer.onNext(value);
|
||
|
|
||
|
this.received = ++this.received % this.observable.windowSize;
|
||
|
if (this.received === 0) {
|
||
|
var self = this;
|
||
|
timeoutScheduler.schedule(function () {
|
||
|
self.observable.source.request(self.observable.windowSize);
|
||
|
});
|
||
|
}
|
||
|
};
|
||
|
|
||
|
windowedObserverPrototype.dispose = function () {
|
||
|
this.observer = null;
|
||
|
if (this.cancel) {
|
||
|
this.cancel.dispose();
|
||
|
this.cancel = null;
|
||
|
}
|
||
|
__sub__.prototype.dispose.call(this);
|
||
|
};
|
||
|
|
||
|
return WindowedObserver;
|
||
|
}(AbstractObserver));
|
||
|
|
||
|
return WindowedObservable;
|
||
|
}(Observable));
|
||
|
|
||
|
/**
|
||
|
* Creates a sliding windowed observable based upon the window size.
|
||
|
* @param {Number} windowSize The number of items in the window
|
||
|
* @returns {Observable} A windowed observable based upon the window size.
|
||
|
*/
|
||
|
ControlledObservable.prototype.windowed = function (windowSize) {
|
||
|
return new WindowedObservable(this, windowSize);
|
||
|
};
|
||
|
|
||
|
return Rx;
|
||
|
}));
|