Skip to content

Commit

Permalink
perf(SafeSubscriber): avoid using Object.create
Browse files Browse the repository at this point in the history
In profiling sessions, the `SafeSubscriber` constructor was showing as hot
function for code that creates and tear downs Observables very frequently.
The usage of `Object.create` attributes to the slowness; avoiding using it
completely removes any overhead of `SafeSubscriber`.

The `Object.create` result was used to call anonymous subscribers in their
own context, i.e. the `this` pointer in `next`/`error`/`complete` is the
anonymous object itself. This commit implements an alternative to achieve
the same effect; binding the subscriber functions to the observer object.
  • Loading branch information
JoostK committed Aug 15, 2020
1 parent 3a850bc commit ea5298f
Showing 1 changed file with 6 additions and 10 deletions.
16 changes: 6 additions & 10 deletions src/internal/Subscriber.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,16 +153,13 @@ 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);
Expand All @@ -171,15 +168,15 @@ export class SafeSubscriber<T> extends Subscriber<T> {
error = (<PartialObserver<T>> observerOrNext).error;
complete = (<PartialObserver<T>> observerOrNext).complete;
if (observerOrNext !== emptyObserver) {
context = Object.create(observerOrNext);
next = next && next.bind(observerOrNext);
error = error && error.bind(observerOrNext);
complete = complete && complete.bind(observerOrNext);
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!;
Expand Down Expand Up @@ -230,7 +227,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);

if (!config.useDeprecatedSynchronousErrorHandling || !_parentSubscriber.syncErrorThrowable) {
this.__tryOrUnsub(wrappedComplete);
Expand All @@ -247,7 +244,7 @@ export class SafeSubscriber<T> extends Subscriber<T> {

private __tryOrUnsub(fn: Function, value?: any): void {
try {
fn.call(this._context, value);
fn.call(this, value);
} catch (err) {
this.unsubscribe();
if (config.useDeprecatedSynchronousErrorHandling) {
Expand All @@ -263,7 +260,7 @@ export class SafeSubscriber<T> extends Subscriber<T> {
throw new Error('bad call');
}
try {
fn.call(this._context, value);
fn.call(this, value);
} catch (err) {
if (config.useDeprecatedSynchronousErrorHandling) {
parent.syncErrorValue = err;
Expand All @@ -280,7 +277,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();
}
Expand Down

0 comments on commit ea5298f

Please sign in to comment.