Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix take and takeLast #5326

Merged
merged 3 commits into from
May 14, 2020
Merged

Fix take and takeLast #5326

merged 3 commits into from
May 14, 2020

Conversation

benlesh
Copy link
Member

@benlesh benlesh commented Feb 26, 2020

Add runtime assertions for JavaScript usage, where invalid argument may be passed causing strange behavior.

BREAKING CHANGE: take will throw a TypeError if called as take() or with an isNaN argument passed.

BREAKING CHANGE: takeLast will throw a TypeError if called as takeLast() or with an isNaN argument passed.

@@ -14,7 +12,28 @@ describe('take operator', () => {
testScheduler = new TestScheduler(observableMatcher);
});

asDiagram('take(2)')('should take two values of an observable with many values', () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IDK what the implications of removing this are.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing, really. I can be put back, but as of right now, we don't run the diagram generation anymore.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll tackle these in another PR, cc @niklas-wortmann

return source.lift(new TakeOperator(count));
}
};
if (isNaN(count)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, not really, Number.isNaN would only return true if it were actually NaN. We want to account for strings that are valid numbers as well. There are people that use RxJS with plain JavaScript (like psychopaths!) and this should be valid for them:

const input = document.querySelector('input[type="text"]');

of(1, 2, 3, 4).pipe(take(input.value)).subscribe(x => console.log(x));

DOES THAT CODE SUCK? Yes. Yes it does. But JS has coercion as a feature. 🤷‍♂

*/
export function takeLast<T>(count: number): MonoTypeOperatorFunction<T> {
if (isNaN(count)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC, that would only return true if it's actually NaN. People could pass in 'blah' there and it would be false if we used Number.isNaN.

spec/operators/takeLast-spec.ts Show resolved Hide resolved
@kievsash
Copy link

kievsash commented Mar 3, 2020

is it expected behavior not to complete if the operator throws because it is used inappropriately?
Actually yeah, too much overhead if to move all checking logic to TakeSubscriber constructor


export function take<T>(count: number): MonoTypeOperatorFunction<T> {
//   if (isNaN(count)) {
//     throw new TypeError(`'count' is not a number`);
//   }
//   if (count < 0) {
//     throw new ArgumentOutOfRangeError;
//   }

  return (source: Observable<T>) =>source.lift(new TakeOperator(count));
}

class TakeOperator<T> implements Operator<T, T> {
  constructor(private count: number) {
  }

  call(subscriber: Subscriber<T>, source: any): TeardownLogic {
    return source.subscribe(new TakeSubscriber(subscriber, this.count));
  }
}

class TakeSubscriber<T> extends Subscriber<T> {
  private _valueCount: number = 0;

  constructor(destination: Subscriber<T>, private count: number) {
    super(destination);

    if (isNaN(count)) {
        this.destination.complete()
        throw new TypeError(`'count' is not a number`);
    }
    if (count < 0) {
        this.destination.complete()
        throw new ArgumentOutOfRangeError;
    }

    if (count === 0) {
        this.destination.complete() // EMPTY
    }
  }

BREAKING CHANGE: take will not throw runtime error for arguments that are negative or NaN, this includes non-TS calls like `take()`.
BREAKING CHANGE: Calling takeLast without arguments or with an argument that is NaN will throw a TypeError
@benlesh benlesh merged commit 5efc474 into ReactiveX:master May 14, 2020
@lock lock bot locked as resolved and limited conversation to collaborators Jun 24, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants