From 6e7f407dfbf1d58c254372b82c23b8096d4be3ee Mon Sep 17 00:00:00 2001 From: Ido Sela Date: Tue, 29 Jan 2019 22:21:26 -0500 Subject: [PATCH] feat(takeWhile): add an `inclusive` option to the operator which causes to emit final value (#4115) By default, the value that causes the predicate to return `false` is not emitted. This change adds an `inclusive` option which changes the behavior of the operator to include the final value which made the predicate return `false`. Typical use case: Polling an API until the response contains a certain value, and then doing something with the response. --- spec-dtslint/operators/takeWhile-spec.ts | 8 +++++++ spec/operators/takeWhile-spec.ts | 15 ++++++++++++ src/internal/operators/takeWhile.ts | 29 +++++++++++++++++------- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/spec-dtslint/operators/takeWhile-spec.ts b/spec-dtslint/operators/takeWhile-spec.ts index 356badce75..3e34f249b3 100644 --- a/spec-dtslint/operators/takeWhile-spec.ts +++ b/spec-dtslint/operators/takeWhile-spec.ts @@ -5,6 +5,14 @@ it('should support a user-defined type guard', () => { const o = of('foo').pipe(takeWhile((s): s is 'foo' => true)); // $ExpectType Observable<"foo"> }); +it('should support a user-defined type guard with inclusive option', () => { + const o = of('foo').pipe(takeWhile((s): s is 'foo' => true, false)); // $ExpectType Observable<"foo"> +}); + it('should support a predicate', () => { const o = of('foo').pipe(takeWhile(s => true)); // $ExpectType Observable }); + +it('should support a predicate with inclusive option', () => { + const o = of('foo').pipe(takeWhile(s => true, true)); // $ExpectType Observable +}); diff --git a/spec/operators/takeWhile-spec.ts b/spec/operators/takeWhile-spec.ts index 8ea128c7d3..fc2da5814c 100644 --- a/spec/operators/takeWhile-spec.ts +++ b/spec/operators/takeWhile-spec.ts @@ -67,6 +67,21 @@ describe('takeWhile operator', () => { expectSubscriptions(e1.subscriptions).toBe(e1subs); }); + it('should take all elements up to and including the element that made ' + + 'the predicate return false', () => { + const e1 = hot('--a-^-b--c--d--e--|'); + const e1subs = '^ ! '; + const expected = '--b--c--(d|) '; + + function predicate(value: string) { + return value !== 'd'; + } + const inclusive = true; + + expectObservable(e1.pipe(takeWhile(predicate, inclusive))).toBe(expected); + expectSubscriptions(e1.subscriptions).toBe(e1subs); + }); + it('should take elements with predicate when source does not complete', () => { const e1 = hot('--a-^-b--c--d--e--'); const e1subs = '^ '; diff --git a/src/internal/operators/takeWhile.ts b/src/internal/operators/takeWhile.ts index 2c4b3167ef..f190e2a544 100644 --- a/src/internal/operators/takeWhile.ts +++ b/src/internal/operators/takeWhile.ts @@ -4,7 +4,8 @@ import { Subscriber } from '../Subscriber'; import { OperatorFunction, MonoTypeOperatorFunction, TeardownLogic } from '../types'; export function takeWhile(predicate: (value: T, index: number) => value is S): OperatorFunction; -export function takeWhile(predicate: (value: T, index: number) => boolean): MonoTypeOperatorFunction; +export function takeWhile(predicate: (value: T, index: number) => value is S, inclusive: false): OperatorFunction; +export function takeWhile(predicate: (value: T, index: number) => boolean, inclusive?: boolean): MonoTypeOperatorFunction; /** * Emits values emitted by the source Observable so long as each value satisfies @@ -42,22 +43,29 @@ export function takeWhile(predicate: (value: T, index: number) => boolean): M * @param {function(value: T, index: number): boolean} predicate A function that * evaluates a value emitted by the source Observable and returns a boolean. * Also takes the (zero-based) index as the second argument. + * @param {boolean} inclusive When set to `true` the value that caused + * `predicate` to return `false` will also be emitted. * @return {Observable} An Observable that emits the values from the source * Observable so long as each value satisfies the condition defined by the * `predicate`, then completes. * @method takeWhile * @owner Observable */ -export function takeWhile(predicate: (value: T, index: number) => boolean): MonoTypeOperatorFunction { - return (source: Observable) => source.lift(new TakeWhileOperator(predicate)); +export function takeWhile( + predicate: (value: T, index: number) => boolean, + inclusive = false): MonoTypeOperatorFunction { + return (source: Observable) => + source.lift(new TakeWhileOperator(predicate, inclusive)); } class TakeWhileOperator implements Operator { - constructor(private predicate: (value: T, index: number) => boolean) { - } + constructor( + private predicate: (value: T, index: number) => boolean, + private inclusive: boolean) {} call(subscriber: Subscriber, source: any): TeardownLogic { - return source.subscribe(new TakeWhileSubscriber(subscriber, this.predicate)); + return source.subscribe( + new TakeWhileSubscriber(subscriber, this.predicate, this.inclusive)); } } @@ -69,8 +77,10 @@ class TakeWhileOperator implements Operator { class TakeWhileSubscriber extends Subscriber { private index: number = 0; - constructor(destination: Subscriber, - private predicate: (value: T, index: number) => boolean) { + constructor( + destination: Subscriber, + private predicate: (value: T, index: number) => boolean, + private inclusive: boolean) { super(destination); } @@ -91,6 +101,9 @@ class TakeWhileSubscriber extends Subscriber { if (Boolean(predicateResult)) { destination.next(value); } else { + if (this.inclusive) { + destination.next(value); + } destination.complete(); } }