diff --git a/spec/operators/debounce-spec.ts b/spec/operators/debounce-spec.ts index 20e6079089..dcff8f230d 100644 --- a/spec/operators/debounce-spec.ts +++ b/spec/operators/debounce-spec.ts @@ -399,4 +399,20 @@ describe('Observable.prototype.debounce', () => { done(new Error('should not be called')); }); }); + + it('should debounce correctly when synchronously reentered', () => { + const results = []; + const source = new Rx.Subject(); + + source.debounce(() => Observable.of(null)).subscribe(value => { + results.push(value); + + if (value === 1) { + source.next(2); + } + }); + source.next(1); + + expect(results).to.deep.equal([1, 2]); + }); }); diff --git a/spec/operators/debounceTime-spec.ts b/spec/operators/debounceTime-spec.ts index 693469d803..ce950e8950 100644 --- a/spec/operators/debounceTime-spec.ts +++ b/spec/operators/debounceTime-spec.ts @@ -1,5 +1,7 @@ +import { expect } from 'chai'; import * as Rx from '../../src/Rx'; import marbleTestingSignature = require('../helpers/marble-testing'); // tslint:disable-line:no-require-imports +import { VirtualTimeScheduler } from '../../src/scheduler/VirtualTimeScheduler'; declare const { asDiagram }; declare const hot: typeof marbleTestingSignature.hot; @@ -153,4 +155,22 @@ describe('Observable.prototype.debounceTime', () => { expectObservable(e1.debounceTime(40, rxTestScheduler)).toBe(expected); expectSubscriptions(e1.subscriptions).toBe(e1subs); }); + + it('should debounce correctly when synchronously reentered', () => { + const results = []; + const source = new Rx.Subject(); + const scheduler = new VirtualTimeScheduler(); + + source.debounceTime(0, scheduler).subscribe(value => { + results.push(value); + + if (value === 1) { + source.next(2); + } + }); + source.next(1); + scheduler.flush(); + + expect(results).to.deep.equal([1, 2]); + }); }); \ No newline at end of file diff --git a/src/operators/debounce.ts b/src/operators/debounce.ts index 4634b48247..ed4637045d 100644 --- a/src/operators/debounce.ts +++ b/src/operators/debounce.ts @@ -129,6 +129,11 @@ class DebounceSubscriber extends OuterSubscriber { subscription.unsubscribe(); this.remove(subscription); } + // This must be done *before* passing the value + // along to the destination because it's possible for + // the value to synchronously re-enter this operator + // recursively if the duration selector Observable + // emits synchronously this.value = null; this.hasValue = false; super._next(value); diff --git a/src/operators/debounceTime.ts b/src/operators/debounceTime.ts index 2c8b8075f5..9ecceada5f 100644 --- a/src/operators/debounceTime.ts +++ b/src/operators/debounceTime.ts @@ -97,9 +97,15 @@ class DebounceTimeSubscriber extends Subscriber { this.clearDebounce(); if (this.hasValue) { - this.destination.next(this.lastValue); + const { lastValue } = this; + // This must be done *before* passing the value + // along to the destination because it's possible for + // the value to synchronously re-enter this operator + // recursively when scheduled with things like + // VirtualScheduler/TestScheduler. this.lastValue = null; this.hasValue = false; + this.destination.next(lastValue); } }