-
Notifications
You must be signed in to change notification settings - Fork 3k
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
perf(SafeSubscriber): avoid using Object.create
#5646
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -153,33 +153,37 @@ export class Subscriber<T> extends Subscription implements Observer<T> { | |
*/ | ||
export class SafeSubscriber<T> extends Subscriber<T> { | ||
|
||
private _context: any; | ||
|
||
constructor(private _parentSubscriber: Subscriber<T>, | ||
observerOrNext?: PartialObserver<T> | ((value: T) => void) | null, | ||
error?: ((e?: any) => void) | null, | ||
complete?: (() => void) | null) { | ||
super(); | ||
|
||
let next: ((value: T) => void) | undefined; | ||
let context: any = this; | ||
|
||
if (isFunction(observerOrNext)) { | ||
next = (<((value: T) => void)> observerOrNext); | ||
next = observerOrNext; | ||
} else if (observerOrNext) { | ||
next = (<PartialObserver<T>> observerOrNext).next; | ||
error = (<PartialObserver<T>> observerOrNext).error; | ||
complete = (<PartialObserver<T>> observerOrNext).complete; | ||
next = observerOrNext.next; | ||
error = observerOrNext.error; | ||
complete = observerOrNext.complete; | ||
if (observerOrNext !== emptyObserver) { | ||
context = Object.create(observerOrNext); | ||
let context: any; | ||
if (config.useDeprecatedNextContext) { | ||
context = Object.create(observerOrNext); | ||
context.unsubscribe = this.unsubscribe.bind(this); | ||
} else { | ||
context = observerOrNext; | ||
} | ||
next = next && next.bind(context); | ||
error = error && error.bind(context); | ||
complete = complete && complete.bind(context); | ||
if (isSubscription(observerOrNext)) { | ||
observerOrNext.add(this.unsubscribe.bind(this)); | ||
} | ||
context.unsubscribe = this.unsubscribe.bind(this); | ||
} | ||
} | ||
|
||
this._context = context; | ||
this._next = next!; | ||
this._error = error!; | ||
this._complete = complete!; | ||
|
@@ -230,7 +234,7 @@ export class SafeSubscriber<T> extends Subscriber<T> { | |
if (!this.isStopped) { | ||
const { _parentSubscriber } = this; | ||
if (this._complete) { | ||
const wrappedComplete = () => this._complete.call(this._context); | ||
const wrappedComplete = () => this._complete.call(this); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue: I think this const wrappedComplete = config.useDeprecatedNextContext
? () => this._complete.call(this)
: this._complete; And I think we need tests for plain callbacks, too. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am about to make a pass through all of this. I'm going to merge this as-is, and address the rest in another PR. |
||
|
||
if (!config.useDeprecatedSynchronousErrorHandling || !_parentSubscriber.syncErrorThrowable) { | ||
this.__tryOrUnsub(wrappedComplete); | ||
|
@@ -247,7 +251,7 @@ export class SafeSubscriber<T> extends Subscriber<T> { | |
|
||
private __tryOrUnsub(fn: Function, value?: any): void { | ||
try { | ||
fn.call(this._context, value); | ||
fn(value); | ||
} catch (err) { | ||
this.unsubscribe(); | ||
if (config.useDeprecatedSynchronousErrorHandling) { | ||
|
@@ -263,7 +267,7 @@ export class SafeSubscriber<T> extends Subscriber<T> { | |
throw new Error('bad call'); | ||
} | ||
try { | ||
fn.call(this._context, value); | ||
fn(value); | ||
} catch (err) { | ||
if (config.useDeprecatedSynchronousErrorHandling) { | ||
parent.syncErrorValue = err; | ||
|
@@ -280,7 +284,6 @@ export class SafeSubscriber<T> extends Subscriber<T> { | |
/** @internal This is an internal implementation detail, do not use. */ | ||
_unsubscribe(): void { | ||
const { _parentSubscriber } = this; | ||
this._context = null; | ||
this._parentSubscriber = null!; | ||
_parentSubscriber.unsubscribe(); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was able to remove this statement without affecting any tests; this makes me a bit unsure on what this was trying to achieve and if it's safe to remove it, although I can't think of what this was supposed to do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And I realized after opening this that without this assignment, it becomes possible to just use
context = observerOrNext
as an alternative to binding the subscriber callbacks. If that is indeed possible (again, no tests start to fail) then I think switching to use that approach makes sense.Edit: this assignment does allow anonymous subscribers to call
this.unsubscribe()
, which this PR would break. Since there's no tests for this, how intentional is that?Furthermore, I'd consider this to cause inconsistencies for anonymous objects that do have<- Actually, that appears to work, resulting in unsubscription and the anonymous' object'sunsubscribe
themselves.unsubscribe
to be called. Here's a small StackBlitz sample.