Skip to content

Commit

Permalink
fix(first): unsubscription logic properly called on complete and error (
Browse files Browse the repository at this point in the history
#2463)

Force unsubscription logic when operator completes or errors, so that source Observable is only subscribed as long as it has to, even when combined with operators that do not immediately unsubscribe from 'first' when it completes or errors.

Closes #2455

BREAKING CHANGE: unsubscription cadence has changed for `first`, this means side-effects related to unsubscription may occur at a different time than in previous versions.
  • Loading branch information
mpodlasin authored and benlesh committed Jun 14, 2017
1 parent 2b4a96c commit c04eb85
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 1 deletion.
6 changes: 5 additions & 1 deletion spec/helpers/doNotUnsubscribe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,8 @@ class DoNotUnsubscribeOperator<T, R> implements Rx.Operator<T, R> {

class DoNotUnsubscribeSubscriber<T> extends Rx.Subscriber<T> {
unsubscribe() {} // tslint:disable-line no-empty
}
<<<<<<< HEAD
}
=======
}
>>>>>>> af747f06... fix(first): unsubscription logic properly called on complete and error (#2463)
36 changes: 36 additions & 0 deletions spec/operators/first-spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {expect} from 'chai';
import * as Rx from '../../dist/cjs/Rx';
import marbleTestingSignature = require('../helpers/marble-testing'); // tslint:disable-line:no-require-imports
import { doNotUnsubscribe } from '../helpers/doNotUnsubscribe';

declare const { asDiagram };
declare const hot: typeof marbleTestingSignature.hot;
Expand Down Expand Up @@ -220,6 +221,41 @@ describe('Observable.prototype.first', () => {
expectSubscriptions(e1.subscriptions).toBe(sub);
});

it('should unsubscribe from the source after first value, even if destination doesn\'t unsubscribe', () => {
const e1 = hot('--^---a---|');
const sub = '^---!';
const expected = '----(a|)';

const result = e1.first().let(doNotUnsubscribe);

expectObservable(result).toBe(expected);
expectSubscriptions(e1.subscriptions).toBe(sub);
});

it('should unsubscribe from the source after completion without value,' +
' even if destination doesn\'t unsubscribe', () => {
const e1 = hot('--^---|');
const sub = '^---!';
const expected = '----(a|)';

const result = e1.first(undefined, undefined, 'a').let(doNotUnsubscribe);

expectObservable(result).toBe(expected);
expectSubscriptions(e1.subscriptions).toBe(sub);
});

it('should unsubscribe from the source after erroring without value,' +
' even if destination doesn\'t unsubscribe', () => {
const e1 = hot('--^---|');
const sub = '^---!';
const expected = '----#';

const result = e1.first().let(doNotUnsubscribe);

expectObservable(result).toBe(expected, undefined, new Rx.EmptyError());
expectSubscriptions(e1.subscriptions).toBe(sub);
});

it('should support type guards without breaking previous behavior', () => {
// tslint:disable no-unused-variable

Expand Down
3 changes: 3 additions & 0 deletions src/operator/first.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ class FirstSubscriber<T, R> extends Subscriber<T> {
this._emitted = true;
destination.next(value);
destination.complete();
this.unsubscribe();
this.hasCompleted = true;
}
}
Expand All @@ -165,8 +166,10 @@ class FirstSubscriber<T, R> extends Subscriber<T> {
if (!this.hasCompleted && typeof this.defaultValue !== 'undefined') {
destination.next(this.defaultValue);
destination.complete();
this.unsubscribe();
} else if (!this.hasCompleted) {
destination.error(new EmptyError);
this.unsubscribe();
}
}
}

0 comments on commit c04eb85

Please sign in to comment.