diff --git a/spec/operators/reduce-spec.ts b/spec/operators/reduce-spec.ts index 654817544a..c8f0f1a364 100644 --- a/spec/operators/reduce-spec.ts +++ b/spec/operators/reduce-spec.ts @@ -35,6 +35,36 @@ describe('Observable.prototype.reduce', () => { expectSubscriptions(e1.subscriptions).toBe(e1subs); }); + it('should reduce with a seed of undefined', () => { + const e1 = hot('--a--^--b--c--d--e--f--g--|'); + const e1subs = '^ !'; + const expected = '---------------------(x|)'; + + const values = { + x: 'undefined b c d e f g' + }; + + const source = e1.reduce((acc: any, x: string) => acc + ' ' + x, undefined); + + expectObservable(source).toBe(expected, values); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + }); + + it('should reduce without a seed', () => { + const e1 = hot('--a--^--b--c--d--e--f--g--|'); + const e1subs = '^ !'; + const expected = '---------------------(x|)'; + + const values = { + x: 'b c d e f g' + }; + + const source = e1.reduce((acc: any, x: string) => acc + ' ' + x); + + expectObservable(source).toBe(expected, values); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + }); + it('should reduce with seed if source is empty', () => { const e1 = hot('--a--^-------|'); const e1subs = '^ !'; diff --git a/spec/operators/scan-spec.ts b/spec/operators/scan-spec.ts index 18755506c1..ed2fc37945 100644 --- a/spec/operators/scan-spec.ts +++ b/spec/operators/scan-spec.ts @@ -43,6 +43,26 @@ describe('Observable.prototype.scan', () => { expectSubscriptions(e1.subscriptions).toBe(e1subs); }); + it('should scan with a seed of undefined', () => { + const e1 = hot('--a--^--b--c--d--e--f--g--|'); + const e1subs = '^ !'; + const expected = '---u--v--w--x--y--z--|'; + + const values = { + u: 'undefined b', + v: 'undefined b c', + w: 'undefined b c d', + x: 'undefined b c d e', + y: 'undefined b c d e f', + z: 'undefined b c d e f g' + }; + + const source = e1.scan((acc: any, x: string) => acc + ' ' + x, undefined); + + expectObservable(source).toBe(expected, values); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + }); + it('should scan without seed', () => { const e1 = hot('--a--^--b--c--d--|'); const e1subs = '^ !'; diff --git a/src/operator/reduce.ts b/src/operator/reduce.ts index 8bae3be8ec..a3a1f38ae5 100644 --- a/src/operator/reduce.ts +++ b/src/operator/reduce.ts @@ -53,16 +53,24 @@ export function reduce(this: Observable, accumulator: (acc: T[], value: T, export function reduce(this: Observable, accumulator: (acc: R, value: T, index: number) => R, seed?: R): Observable; /* tslint:disable:max-line-length */ export function reduce(this: Observable, accumulator: (acc: R, value: T) => R, seed?: R): Observable { - return this.lift(new ReduceOperator(accumulator, seed)); + let hasSeed = false; + // providing a seed of `undefined` *should* be valid and trigger + // hasSeed! so don't use `seed !== undefined` checks! + // For this reason, we have to check it here at the original call site + // otherwise inside Operator/Subscriber we won't know if `undefined` + // means they didn't provide anything or if they literally provided `undefined` + if (arguments.length >= 2) { + hasSeed = true; + } + + return this.lift(new ReduceOperator(accumulator, seed, hasSeed)); } export class ReduceOperator implements Operator { - - constructor(private accumulator: (acc: R, value: T) => R, private seed?: R) { - } + constructor(private accumulator: (acc: R, value: T) => R, private seed?: R, private hasSeed: boolean = false) {} call(subscriber: Subscriber, source: any): any { - return source._subscribe(new ReduceSubscriber(subscriber, this.accumulator, this.seed)); + return source._subscribe(new ReduceSubscriber(subscriber, this.accumulator, this.seed, this.hasSeed)); } } @@ -72,18 +80,15 @@ export class ReduceOperator implements Operator { * @extends {Ignored} */ export class ReduceSubscriber extends Subscriber { - acc: T | R; - hasSeed: boolean; hasValue: boolean = false; constructor(destination: Subscriber, private accumulator: (acc: R, value: T) => R, - seed?: R) { + seed: R, + private hasSeed: boolean) { super(destination); this.acc = seed; - this.accumulator = accumulator; - this.hasSeed = typeof seed !== 'undefined'; } protected _next(value: T) { diff --git a/src/operator/scan.ts b/src/operator/scan.ts index 452b16742a..99e771a77c 100644 --- a/src/operator/scan.ts +++ b/src/operator/scan.ts @@ -45,15 +45,24 @@ export function scan(this: Observable, accumulator: (acc: T[], value: T, i export function scan(this: Observable, accumulator: (acc: R, value: T, index: number) => R, seed?: R): Observable; /* tslint:disable:max-line-length */ export function scan(this: Observable, accumulator: (acc: R, value: T, index: number) => R, seed?: T | R): Observable { - return this.lift(new ScanOperator(accumulator, seed)); + let hasSeed = false; + // providing a seed of `undefined` *should* be valid and trigger + // hasSeed! so don't use `seed !== undefined` checks! + // For this reason, we have to check it here at the original call site + // otherwise inside Operator/Subscriber we won't know if `undefined` + // means they didn't provide anything or if they literally provided `undefined` + if (arguments.length >= 2) { + hasSeed = true; + } + + return this.lift(new ScanOperator(accumulator, seed, hasSeed)); } class ScanOperator implements Operator { - constructor(private accumulator: (acc: R, value: T, index: number) => R, private seed?: T | R) { - } + constructor(private accumulator: (acc: R, value: T, index: number) => R, private seed?: T | R, private hasSeed: boolean = false) {} call(subscriber: Subscriber, source: any): any { - return source._subscribe(new ScanSubscriber(subscriber, this.accumulator, this.seed)); + return source._subscribe(new ScanSubscriber(subscriber, this.accumulator, this.seed, this.hasSeed)); } } @@ -64,26 +73,22 @@ class ScanOperator implements Operator { */ class ScanSubscriber extends Subscriber { private index: number = 0; - private accumulatorSet: boolean = false; - private _seed: T | R; get seed(): T | R { return this._seed; } set seed(value: T | R) { - this.accumulatorSet = true; + this.hasSeed = true; this._seed = value; } - constructor(destination: Subscriber, private accumulator: (acc: R, value: T, index: number) => R, seed?: T | R) { + constructor(destination: Subscriber, private accumulator: (acc: R, value: T, index: number) => R, private _seed: T | R, private hasSeed: boolean) { super(destination); - this.seed = seed; - this.accumulatorSet = typeof seed !== 'undefined'; } protected _next(value: T): void { - if (!this.accumulatorSet) { + if (!this.hasSeed) { this.seed = value; this.destination.next(value); } else {