Skip to content

Commit 598e9ce

Browse files
jayphelpsbenlesh
authored andcommitted
fix(debounceTime): synchronous reentrancy of debounceTime no longer swallows the second value (#3218)
fixes #2748
1 parent c1721e3 commit 598e9ce

File tree

4 files changed

+48
-1
lines changed

4 files changed

+48
-1
lines changed

spec/operators/debounce-spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,4 +399,20 @@ describe('Observable.prototype.debounce', () => {
399399
done(new Error('should not be called'));
400400
});
401401
});
402+
403+
it('should debounce correctly when synchronously reentered', () => {
404+
const results = [];
405+
const source = new Rx.Subject();
406+
407+
source.debounce(() => Observable.of(null)).subscribe(value => {
408+
results.push(value);
409+
410+
if (value === 1) {
411+
source.next(2);
412+
}
413+
});
414+
source.next(1);
415+
416+
expect(results).to.deep.equal([1, 2]);
417+
});
402418
});

spec/operators/debounceTime-spec.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import { expect } from 'chai';
12
import * as Rx from '../../src/Rx';
23
import marbleTestingSignature = require('../helpers/marble-testing'); // tslint:disable-line:no-require-imports
4+
import { VirtualTimeScheduler } from '../../src/scheduler/VirtualTimeScheduler';
35

46
declare const { asDiagram };
57
declare const hot: typeof marbleTestingSignature.hot;
@@ -153,4 +155,22 @@ describe('Observable.prototype.debounceTime', () => {
153155
expectObservable(e1.debounceTime(40, rxTestScheduler)).toBe(expected);
154156
expectSubscriptions(e1.subscriptions).toBe(e1subs);
155157
});
158+
159+
it('should debounce correctly when synchronously reentered', () => {
160+
const results = [];
161+
const source = new Rx.Subject();
162+
const scheduler = new VirtualTimeScheduler();
163+
164+
source.debounceTime(0, scheduler).subscribe(value => {
165+
results.push(value);
166+
167+
if (value === 1) {
168+
source.next(2);
169+
}
170+
});
171+
source.next(1);
172+
scheduler.flush();
173+
174+
expect(results).to.deep.equal([1, 2]);
175+
});
156176
});

src/operators/debounce.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,11 @@ class DebounceSubscriber<T, R> extends OuterSubscriber<T, R> {
129129
subscription.unsubscribe();
130130
this.remove(subscription);
131131
}
132+
// This must be done *before* passing the value
133+
// along to the destination because it's possible for
134+
// the value to synchronously re-enter this operator
135+
// recursively if the duration selector Observable
136+
// emits synchronously
132137
this.value = null;
133138
this.hasValue = false;
134139
super._next(value);

src/operators/debounceTime.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,15 @@ class DebounceTimeSubscriber<T> extends Subscriber<T> {
9797
this.clearDebounce();
9898

9999
if (this.hasValue) {
100-
this.destination.next(this.lastValue);
100+
const { lastValue } = this;
101+
// This must be done *before* passing the value
102+
// along to the destination because it's possible for
103+
// the value to synchronously re-enter this operator
104+
// recursively when scheduled with things like
105+
// VirtualScheduler/TestScheduler.
101106
this.lastValue = null;
102107
this.hasValue = false;
108+
this.destination.next(lastValue);
103109
}
104110
}
105111

0 commit comments

Comments
 (0)