import { Subject, SubjectSubscriber } from '../Subject'; import { Operator } from '../Operator'; import { Observable } from '../Observable'; import { Subscriber } from '../Subscriber'; import { Subscription, TeardownLogic } from '../Subscription'; /** * @class ConnectableObservable */ export class ConnectableObservable extends Observable { protected _subject: Subject; protected _refCount: number = 0; protected _connection: Subscription; constructor(protected source: Observable, protected subjectFactory: () => Subject) { super(); } protected _subscribe(subscriber: Subscriber) { return this.getSubject().subscribe(subscriber); } protected getSubject(): Subject { const subject = this._subject; if (!subject || subject.isStopped) { this._subject = this.subjectFactory(); } return this._subject; } connect(): Subscription { let connection = this._connection; if (!connection) { connection = this._connection = new Subscription(); connection.add(this.source .subscribe(new ConnectableSubscriber(this.getSubject(), this))); if (connection.closed) { this._connection = null; connection = Subscription.EMPTY; } else { this._connection = connection; } } return connection; } refCount(): Observable { return this.lift(new RefCountOperator(this)); } } export const connectableObservableDescriptor: PropertyDescriptorMap = { operator: { value: null }, _refCount: { value: 0, writable: true }, _subscribe: { value: ( ConnectableObservable.prototype)._subscribe }, getSubject: { value: ( ConnectableObservable.prototype).getSubject }, connect: { value: ( ConnectableObservable.prototype).connect }, refCount: { value: ( ConnectableObservable.prototype).refCount } }; class ConnectableSubscriber extends SubjectSubscriber { constructor(destination: Subject, private connectable: ConnectableObservable) { super(destination); } protected _error(err: any): void { this._unsubscribe(); super._error(err); } protected _complete(): void { this._unsubscribe(); super._complete(); } protected _unsubscribe() { const { connectable } = this; if (connectable) { this.connectable = null; const connection = ( connectable)._connection; ( connectable)._refCount = 0; ( connectable)._subject = null; ( connectable)._connection = null; if (connection) { connection.unsubscribe(); } } } } class RefCountOperator implements Operator { constructor(private connectable: ConnectableObservable) { } call(subscriber: Subscriber, source: any): TeardownLogic { const { connectable } = this; ( connectable)._refCount++; const refCounter = new RefCountSubscriber(subscriber, connectable); const subscription = source.subscribe(refCounter); if (!refCounter.closed) { ( refCounter).connection = connectable.connect(); } return subscription; } } class RefCountSubscriber extends Subscriber { private connection: Subscription; constructor(destination: Subscriber, private connectable: ConnectableObservable) { super(destination); } protected _unsubscribe() { const { connectable } = this; if (!connectable) { this.connection = null; return; } this.connectable = null; const refCount = ( connectable)._refCount; if (refCount <= 0) { this.connection = null; return; } ( connectable)._refCount = refCount - 1; if (refCount > 1) { this.connection = null; return; } /// // Compare the local RefCountSubscriber's connection Subscription to the // connection Subscription on the shared ConnectableObservable. In cases // where the ConnectableObservable source synchronously emits values, and // the RefCountSubscriber's downstream Observers synchronously unsubscribe, // execution continues to here before the RefCountOperator has a chance to // supply the RefCountSubscriber with the shared connection Subscription. // For example: // ``` // Observable.range(0, 10) // .publish() // .refCount() // .take(5) // .subscribe(); // ``` // In order to account for this case, RefCountSubscriber should only dispose // the ConnectableObservable's shared connection Subscription if the // connection Subscription exists, *and* either: // a. RefCountSubscriber doesn't have a reference to the shared connection // Subscription yet, or, // b. RefCountSubscriber's connection Subscription reference is identical // to the shared connection Subscription /// const { connection } = this; const sharedConnection = ( connectable)._connection; this.connection = null; if (sharedConnection && (!connection || sharedConnection === connection)) { sharedConnection.unsubscribe(); } } }