From 6416935ab03b38d7048d0641c8826ba5311eae8c Mon Sep 17 00:00:00 2001
From: Ben Lesh <ben@benlesh.com>
Date: Thu, 10 Sep 2020 17:56:39 -0500
Subject: [PATCH] refactor(Subscription): minor size reduction (#5707)

- Reduces the size of `Subscription` slightly.
- Moves back to a single property for single parent, many parents, scenarios
- Updates the type of `Teardown` to not be so loose. `Function` shouldn't be used.
---
 api_guard/dist/types/index.d.ts |  2 +-
 src/internal/Subscription.ts    | 83 ++++++++++++---------------------
 src/internal/types.ts           |  2 +-
 3 files changed, 32 insertions(+), 55 deletions(-)

diff --git a/api_guard/dist/types/index.d.ts b/api_guard/dist/types/index.d.ts
index 6d47f7e5ae..fcc54fcd84 100644
--- a/api_guard/dist/types/index.d.ts
+++ b/api_guard/dist/types/index.d.ts
@@ -581,7 +581,7 @@ export interface SubscriptionLike extends Unsubscribable {
 
 export declare type Tail<X extends any[]> = ((...args: X) => any) extends ((arg: any, ...rest: infer U) => any) ? U : never;
 
-export declare type TeardownLogic = Subscription | Unsubscribable | Function | void;
+export declare type TeardownLogic = Subscription | Unsubscribable | (() => void) | void;
 
 export declare function throwError(errorFactory: () => any): Observable<never>;
 export declare function throwError(error: any): Observable<never>;
diff --git a/src/internal/Subscription.ts b/src/internal/Subscription.ts
index a9fd3ba72e..22bcff7fa2 100644
--- a/src/internal/Subscription.ts
+++ b/src/internal/Subscription.ts
@@ -1,7 +1,7 @@
 /** @prettier */
 import { isFunction } from './util/isFunction';
 import { UnsubscriptionError } from './util/UnsubscriptionError';
-import { SubscriptionLike, TeardownLogic } from './types';
+import { SubscriptionLike, TeardownLogic, Unsubscribable } from './types';
 
 /**
  * Represents a disposable resource, such as the execution of an Observable. A
@@ -27,11 +27,7 @@ export class Subscription implements SubscriptionLike {
    */
   public closed = false;
 
-  /** If this subscription has been added to one parent, it will show up here */
-  private _singleParent: Subscription | null = null;
-
-  /** If this subscription has been added to more than one parent, they will show up here. */
-  private _parents: Subscription[] | null = null;
+  private _parentage: Subscription[] | Subscription | null = null;
 
   /**
    * The list of registered teardowns to execute upon unsubscription. Adding and removing from this
@@ -58,17 +54,13 @@ export class Subscription implements SubscriptionLike {
       this.closed = true;
 
       // Remove this from it's parents.
-
-      const { _singleParent } = this;
-      let _parents: Subscription[] | null;
-      if (_singleParent) {
-        this._singleParent = null;
-        _singleParent.remove(this);
-      } else if ((_parents = this._parents)) {
-        this._parents = null;
-        for (const parent of _parents) {
+      const { _parentage } = this;
+      if (Array.isArray(_parentage)) {
+        for (const parent of _parentage) {
           parent.remove(this);
         }
+      } else {
+        _parentage?.remove(this);
       }
 
       const { initialTeardown } = this;
@@ -81,15 +73,11 @@ export class Subscription implements SubscriptionLike {
       }
 
       const { _teardowns } = this;
-      this._teardowns = null;
       if (_teardowns) {
+        this._teardowns = null;
         for (const teardown of _teardowns) {
           try {
-            if (typeof teardown === 'function') {
-              teardown();
-            } else {
-              teardown.unsubscribe();
-            }
+            execTeardown(teardown);
           } catch (err) {
             errors = errors ?? [];
             if (err instanceof UnsubscriptionError) {
@@ -132,11 +120,7 @@ export class Subscription implements SubscriptionLike {
       if (this.closed) {
         // If this subscription is already closed,
         // execute whatever teardown is handed to it automatically.
-        if (typeof teardown === 'function') {
-          teardown();
-        } else {
-          teardown.unsubscribe();
-        }
+        execTeardown(teardown);
       } else {
         if (teardown instanceof Subscription) {
           // We don't add closed subscriptions, and we don't add the same subscription
@@ -146,8 +130,7 @@ export class Subscription implements SubscriptionLike {
           }
           teardown._addParent(this);
         }
-        this._teardowns = this._teardowns ?? [];
-        this._teardowns.push(teardown);
+        (this._teardowns = this._teardowns ?? []).push(teardown);
       }
     }
   }
@@ -158,7 +141,8 @@ export class Subscription implements SubscriptionLike {
    * @param parent the parent to check for
    */
   private _hasParent(parent: Subscription) {
-    return this._singleParent === parent || this._parents?.includes(parent) || false;
+    const { _parentage } = this;
+    return _parentage === parent || (Array.isArray(_parentage) && _parentage.includes(parent));
   }
 
   /**
@@ -169,20 +153,8 @@ export class Subscription implements SubscriptionLike {
    * @param parent The parent subscription to add
    */
   private _addParent(parent: Subscription) {
-    const { _singleParent } = this;
-    let _parents: Subscription[] | null;
-    if (_singleParent) {
-      // We already have one parent so we'll need to expand
-      // to use an array
-      this._parents = [_singleParent, parent];
-      this._singleParent = null;
-    } else if ((_parents = this._parents)) {
-      // We already have more than one parent, so just add on to that array.
-      _parents.push(parent);
-    } else {
-      // This is our first parent.
-      this._singleParent = parent;
-    }
+    const { _parentage } = this;
+    this._parentage = Array.isArray(_parentage) ? (_parentage.push(parent), _parentage) : _parentage ? [_parentage, parent] : parent;
   }
 
   /**
@@ -190,16 +162,13 @@ export class Subscription implements SubscriptionLike {
    * @param parent The parent to remove
    */
   private _removeParent(parent: Subscription) {
-    const { _singleParent } = this;
-    let _parents: Subscription[] | null;
-    if (_singleParent) {
-      if (_singleParent === parent) {
-        this._singleParent = null;
-      }
-    } else if ((_parents = this._parents)) {
-      const index = _parents.indexOf(parent);
-      if (index >= 0) {
-        _parents.splice(index, 1);
+    const { _parentage } = this;
+    if (_parentage === parent) {
+      this._parentage = null;
+    } else if (Array.isArray(_parentage)) {
+      const index = _parentage.indexOf(parent);
+      if (0 <= index) {
+        _parentage.splice(index, 1);
       }
     }
   }
@@ -243,3 +212,11 @@ export function isSubscription(value: any): value is Subscription {
       typeof value.unsubscribe === 'function')
   );
 }
+
+function execTeardown(teardown: Unsubscribable | (() => void)) {
+  if (typeof teardown === 'function') {
+    teardown();
+  } else {
+    teardown.unsubscribe();
+  }
+}
diff --git a/src/internal/types.ts b/src/internal/types.ts
index 2ddd7004fa..a6fbd8f9c9 100644
--- a/src/internal/types.ts
+++ b/src/internal/types.ts
@@ -51,7 +51,7 @@ export interface Unsubscribable {
   unsubscribe(): void;
 }
 
-export type TeardownLogic = Subscription | Unsubscribable | Function | void;
+export type TeardownLogic = Subscription | Unsubscribable | (() => void) | void;
 
 export interface SubscriptionLike extends Unsubscribable {
   unsubscribe(): void;