RxJS 源代码学习(三)—— Subscription

1,690 阅读9分钟

什么是 Subscription

从语言直觉上来看,Subscription 是对 Observable 对象进行 subscribe 这一行为的描述。RxJS 给的精确定义是:

Represents a disposable resource, such as the execution of an Observable. A Subscription has one important method, unsubscribe, that takes no argument and just disposes the resource held by the subscription.

也就是说,Subscription 代表一种可销毁的资源,比如对一个 Observable 对象的执行;其包含一个重要方法,即unscubscribe(),用于销毁 Subscription 所持有的资源。如此描述实在抽象,令人摸不着头脑,不如依旧从示例入手:

import { Observable, Subscription } from 'rxjs';

// Create a lazy Push System
const observable = new Observable(subscriber => {
    subscriber.next(1);
    subscriber.next(2);
    subscriber.next(3);
    subscriber.complete();
});

const pseudoSubscriber = {
    next: (value: number) => console.log('we got', value);
    error: (error: any) => console.error(error);
    complete: () => console.log('completed');
}

// Subscribe the lazy Push System
const subscription = observable.subscribe(pseudoSubscriber);
subscription.unsubscribe();

如上示例引自 RxJS 源代码学习(二)—— Observable,不同的是,针对 observable 对象的订阅,我们将其赋值给变量 subscription,其类型为Subscription。回顾 Observable.subscribe()方法,其函数签名如下所示:

subscribe(
    observerOrNext?: Partial<Observer<T>> | ((value: T) => void) | null,
    error?: ((error: any) => void) | null,
    complete?: (() => void) | null
): Subscription;

函数subscribe()输出类型为Subscription,而实质上该函数所 return 的值,要么是一个临时新创建的 SafeSubscriber 对象,要么是一个类 Subscription 对象:

class PseudoSubscription {
    public closed: boolean;
    
    next() {};
    error() {};
    complete() {};
    remove() {};
    add() {};
    unsubscribe() {};
}

换句话说,只要存在一个对象满足上述定义,那么 RxJS 就认定该对象为一个 Subscription 对象,或者更为精确的,Subscriber 对象。二者关系如下所示:

class Subscriber<T> extends Subscription implements Observer<T> {};

也就是说,在理解 Subscriber 对象之前,首先需要学习Subscription 对象,其定义如下:

class Subscription implements SubscriptionLike {};

export interface SubscriptionLike extends Unsubscribable {
    unsubscribe(): void;
    readonly closed: boolean;
}

其中,布尔类型属性 closed 用于标识该 Subcription 对象是否已取消订阅,关键则在于对退订函数 unsubscribe() 的实现,我们一步一步学习其逻辑实现:

class Subscription implements SubscriptionLike {
    public closed: boolean = false;

    unsubscribe(): void {
        if (!this.closed) {
            this.cloesed = true;
            /** ... */
        }
    }
};

结合上述示例,通过向控制台打印变量subscription,可知变量 subscription 实质上是一个新创的 SafeSubscriber 对象:

至于 SafeSubscriber 对象,其与 Subscriber 的关系如下所示:

class SafeSubscriber<T> extends Subscriber<T> {};

换句话说,我们已经绕不过Subscriber对象,不得不先深入学习和理解,才能进一步搞清楚到底什么是 Subscription

Subscriber 和 SafeSubscriber

顺着 Observable.sunscribe() 的思路,基于传入对象 pseudoSubscriber ,订阅函数会创建一个新的 SafeSubscriber 对象:

class SafeSubscriber<T> extends Subscriber<T> {
    constructor(
        observerOrNext?: Partial<Observer<T>> | ((value: T) => void) | null,
        error?: ((e?: any) => void) | null,
        complete?: (() => void) | null
    ) {
        super();
        let next: ((value: T) => void) | undefined;
        if (isFunction(observerOrNext)) {
            // The first argument is a function, not an observer. The next
            // two arguments *could* be observers, or they could be empty.
            next = observerOrNext;
        } else if (observerOrNext) {
            // The first argument is an observer object, we have to pull the handlers
            // off and capture the owner object as the context. That is because we're
            // going to put them all in a new destination with ensured methods
            // for `next`, `error`, and `complete`. That's part of what makes this
            // the "Safe" Subscriber.
            ({ next, error, complete } = observerOrNext);
            let context: any;
            if (this && config.useDeprecatedNextContext) {
                // This is a deprecated path that made `this.unsubscribe()` available in
                // next handler functions passed to subscribe. This only exists behind a flag
                // now, as it is *very* slow.
                context = Object.create(observerOrNext);
                context.unsubscribe = () => this.unsubscribe();
            } else {
                context = observerOrNext;
            }
            next = next?.bind(context);
            error = error?.bind(context);
            complete = complete?.bind(context);
        }

        // Once we set the destination, the superclass `Subscriber` will
        // do it's magic in the `_next`, `_error`, and `_complete` methods.
        this.destination = {
            next: next ? wrapForErrorHandling(next, this) : noop,
            error: wrapForErrorHandling(error ?? defaultErrorHandler, this),
            complete: complete ? wrapForErrorHandling(complete, this) : noop,
        };
    }
}

第一步,执行super(),即:

class Subscriber<T> extends Subscription implements Observer<T> {
    /**
     * @deprecated
     */
    protected destination: Subscriber<any> | Observer<any>;
    
    /**
     * @deprecated
     */
    constructor(destination?: Subscriber<any> | Observer<any>) {
        super();
        if (destination) {
            this.destination = destination;
            // Automatically chain subscriptions together here.
            // if destination is a Subscription, then it is a Subscriber.
            if (isSubscription(destination)) {
                destination.add(this);
            }
        } else {
            this.destination = EMPTY_OBSERVER;
        }
    }
    
    /** ... */

}

在 RxJS 源代码中,Subscriber 对象的属性 destination 及其构造函数均被标注了「废弃」,不过代码尚在执行,我们可以暂且忽略。实例化 Subscriber 对象同样需要执行super(),不过其作用仅仅用于构造 Subscription 对象,而不会创建或修改数据,原因在于:

class Subscription implements SubscriptionLike {
    /** ... */
    constructor(private initialTeardown?: () => void) {}
    /** ... */
}

由于实例化 SafeSubscriber 对象时执行的 super() 函数并未传入 destination 参数,因此直接赋值 EMPTY_OBSERVER 给 this.destination,即:

this.destination = {
    closed: true,
    next: noop,
    error: defaultErrorHandler,
    complete: noop,
}

EMPTY_OBSERVER 作为常量定义在 Subscriber.ts 文件末尾,由此我们获得了 this.destination 的初始值。接下来,SafeSubscriber 对象的构造函数,基于传入的参数,对 this.destination 进行覆盖。我们不必过于关注函数wrapForErrorHandling(),其本质是为回调函数增加一道名为 Error Handling 的保险,倒是一个很值得学习的日常编码小技巧:

function wrapForErrorHandling(handler: (arg?: any) => void, instance: SafeSubscriber<any>) {
  return (...args: any[]) => {
    try {
      handler(...args);
    } catch (err) {
      if (config.useDeprecatedSynchronousErrorHandling) {
        // If the user has opted for "super-gross" mode, we need to check to see
        // if we're currently subscribing. If we are, we need to mark the _syncError
        // So that it can be rethrown in the `subscribe` call on `Observable`.
        if ((instance as any)._syncErrorHack_isSubscribing) {
          (instance as any).__syncError = err;
        } else {
          // We're not currently subscribing, but we're in super-gross mode,
          // so throw it immediately.
          throw err;
        }
      } else {
        // Ideal path, we report this as an unhandled error,
        // which is thrown on a new call stack.
        reportUnhandledError(err);
      }
    }
  };

随之,this.destination 的值就变成了:

this.destination = {
    closed: true,
    next: wrapForErrorHandling(next, this),
    error: wrapForErrorHandling(error ?? defaultErrorHandler, this),
    complete: wrapForErrorHandling(complete, this),
}

SafeSubscriber 对象的作用仅限于此,正是由于 this.destination 的存在,使得 Safe 成为可能。接下来,我们重点看 Subscriber 对象,除了 closed 属性,其内部还存在另一个 isStopped 属性,用于标识数据流是否结束,即:

class Subscriber<T> extends Subscription implements Observer<T> {
    /**
     * @deprecated
     */
    protected isStopped: boolean = false; 

    /** ... */
}

属性 isStopped 的作用在于,判断 next 函数、error 函数和 complete 函数是否能够执行,即:

class Subscriber<T> extends Subscription implements Observer<T> {
    /** ... */
    next(value?: T): void {
        if (this.isStopped) {
            handleStoppedNotification(nextNotification(value), this);
        } else {
            this._next(value!);
        }
    }
    
    error(err?: any): void {
        if (this.isStopped) {
            handleStoppedNotification(errorNotification(err), this);
        } else {
            this.isStopped = true;
            this._error(err);
        }
    }
    
    complete(): void {
        if (this.isStopped) {
            handleStoppedNotification(COMPLETE_NOTIFICATION, this);
        } else {
            this.isStopped = true;
            this._complete();
        }
    }
    
    unsubscribe(): void {
        if (!this.closed) {
            this.isStopped = true;
            super.unsubscribe();
            this.destination = null!;
        }
    }
    /** ... */
}

设若数据流尚未完成或退订,那么无论执行 next 函数、error 函数还是 complete 函数,均可以执行对应的 _next 函数、_error 函数和 _complete 函数:

class Subscriber<T> extends Subscription implements Observer<T> {
    /** ... */
    protected _next(value: T): void {
        this.destination.next(value);
    }

    protected _error(err: any): void {
        try {
            this.destination.error(err);
        } finally {
            this.unsubscribe();
        }
    }
    
    protected _complete(): void {
        try {
            this.destination.complete();
        } finally {
            this.unsubscribe();
        }
    }

    /** ... */
}

很明显,Subscriber 对象执行 next 函数、error 函数和 complete 函数,最终会调用 SafeSubscriber 对象的 destination 内部的 next 函数、error 函数和 complete 函数,this.destination 与 Subscriber 对象形成了一层代理关系。

当数据流完成时,Subscriber 将isStopped 的值设为 true;此外,若是数据流尚未完成,直接对数据流进行退订操作,同样会将isStopped 的值设为 true,并将 this.destination 设置为 null 值。最最关键的是,需要调用 super``.unsubscribe(),由此,我们已然可以进入到本文真正的主题 —— Subscription。

深入理解 Subscription

想象我们的眼睛变成了一把筛子,将 Subscriber 对象的属性和方法筛除,剩下的就是 Subscription 需要关注的内容:

由于之前创建 SafeSubscriber 对象实例时,调用 super() 函数并未传入任何参数,因而,对象属性 initialTeardown 的值为 undefined;此外,私有属性 _parentage 和 ~teardowns 值均为空值,本质原因在于示例代码尚不足以用到这些属性。因此,我们对示例代码进行一番修改:

import { Observable, Subscription, interval } from 'rxjs';

// Create a lazy Push System
const observable = new Observable(subscriber => {
    subscriber.next(1);
    subscriber.next(2);
    subscriber.next(3);
    subscriber.complete();
});

const pseudoSubscriber = {
    next: (value: number) => console.log('we got', value);
    error: (error: any) => console.error(error);
    complete: () => console.log('completed');
}

const childObservable = interval(1000);
const childSubscription = childObservable.subscribe(x => console.log('child: ' + x))

// Subscribe the lazy Push System
const subscription = observable.subscribe(pseudoSubscriber);

subscription.add(childSubscription);

setTimeout(() => {
    // Unsubscribes BOTH subscription and childSubscription
    subscription.unsubscribe();
}, 10000);

再次打印变量 subscription

不难发现,其(SafeSubscriber 对象 A) _teardowns 属性的值变成了一个包含另一个 SafeSubscriber 对象 B 的数组,而该数组内的唯一元素,其 _parentage 属性的值变成了SafeSubscriber 对象 A,其中的关键所在,即是 add() 方法:

class Subscription implements SubscriptionLike {
    /** ... */
    add(teardown: TeardownLogic): void {
        // Only add the teardown if it's not undefined
        // and don't add a subscription to itself.
        if (teardown && teardown !== this) {
            if (this.closed) {
                // If this subscription is already closed,
                // execute whatever teardown is handed to it automatically.
                execTeardown(teardown);
            } else {
                if (teardown instanceof Subscription) {
                    // We don't add closed subscriptions, and we don't add the same subscription
                    // twice. Subscription unsubscribe is idempotent.
                    if (teardown.closed || teardown._hasParent(this)) {
                        return;
                    }
                    teardown._addParent(this);
                }
                (this._teardowns = this._teardowns ?? []).push(teardown);
            }
        }
    }
    
    private _hasParent(parent: Subscription) {
        const { _parentage } = this;
        return _parentage === parent || (Array.isArray(_parentage) && _parentage.includes(parent));
    }

    private _addParent(parent: Subscription) {
        const { _parentage } = this;
        this._parentage = Array.isArray(_parentage) ? (_parentage.push(parent), _parentage) : _parentage ? [_parentage, parent] : parent;
    }
    /** ... */
}

add() 方法执行之前,会分别对当前 Subscription 和待新增的 Subscription 进行检查,若是其 closed 属性各自被标记为true,则直接退订待新增的 Subscription 或不作处理;此外,若待新增的订阅对象已经拥有了 _parentage,则同样不作处理,可以说“杜绝了「三姓家奴」的投靠”。若是待新增的订阅对象身家清白,当前 Subscription 又尚未退订,则可以执行新增操作:将待新增的 Subscription 插入到当前 Subscription 的 _teardowns 属性内,以及为待新增的 Subscription 的 _parentage 值设为当前的 Subscription。

即然有 add(),那必然有 remove() 方法:

class Subscription implements SubscriptionLike {
    /** ... */
    private _removeParent(parent: Subscription) {
        const { _parentage } = this;
        if (_parentage === parent) {
            this._parentage = null;
        } else if (Array.isArray(_parentage)) {
            arrRemove(_parentage, parent);
        }
    }

    remove(teardown: Exclude<TeardownLogic, void>): void {
        const { _teardowns } = this;
        _teardowns && arrRemove(_teardowns, teardown);

        if (teardown instanceof Subscription) {
            teardown._removeParent(this);
        }
    }

    /** ... */
}

同样,当前 Subscription 清除 _teardowns 属性内的订阅对象,而待删除的订阅对象同样需要删除 _parentage 内的值,二者撇清关系。

正是由于不同订阅之间存在着父子关系,当调用 unsubscribe() 方法时,需要分别移除 _parentage 和 teardowns 中的值:

class Subscription implements SubscriptionLike {
    public closed: boolean = false;

    unsubscribe(): void {
        if (!this.closed) {
            this.cloesed = true;

            const { _parentage } = this;
            if (_parentage) {
                this._parentage = null;
                if (Array.isArray(_parentage)) {
                for (const parent of _parentage) {
                    parent.remove(this);
                }
            } else {
                _parentage.remove(this);
            }
            
            const { _teardowns } = this;
            if (_teardowns) {
                this._teardowns = null;
                for (const teardown of _teardowns) {
                    try {
                        execTeardown(teardown);
                    } catch (err) {
                        errors = errors ?? [];
                        if (err instanceof UnsubscriptionError) {
                            errors = [...errors, ...err.errors];
                        } else {
                            errors.push(err);
                        }
                    }
                }
            }
            
           /** 。。。 */
            
            if (errors) {
                throw new UnsubscriptionError(errors);
            }
        }
    }
};

等等,我们似乎遗漏了 initialTeardown,其签名如下:

initialTeardown?: () => void

我们或可以理解为 Subscription 对象退订阅时所期望执行的函数,由此我们补全了 unsubscribe() 方法的最后一块拼图:

 class Subscription implements SubscriptionLike {
    public closed: boolean = false;
   
    unsubscribe(): void {
        if (!this.closed) {
            this.cloesed = true;
            
            const { _parentage } = this;
            if (_parentage) {
                this._parentage = null;
                if (Array.isArray(_parentage)) {
                for (const parent of _parentage) {
                    parent.remove(this);
                }
            } else {
                _parentage.remove(this);
            }
            
            const { _teardowns } = this;
            if (_teardowns) {
                this._teardowns = null;
                for (const teardown of _teardowns) {
                    try {
                        execTeardown(teardown);
                    } catch (err) {
                        errors = errors ?? [];
                        if (err instanceof UnsubscriptionError) {
                        errors = [...errors, ...err.errors];
                    } else {
                        errors.push(err);
                    }
                }
            }
            
            const { initialTeardown } = this;
            if (isFunction(initialTeardown)) {
                try {
                    initialTeardown();
                } catch (e) {
                    errors = e instanceof UnsubscriptionError ? e.errors : [e];
                }
            }
            
            if (errors) {
                throw new UnsubscriptionError(errors);
            }
        }
    }
};

到此,我们对 RxJS 的核心概念之二 Observable 和 Subscription 之间建立起了清晰的脉络,基本把握了一段简单 RxJS 逻辑的每一个细节(做不到要复习哦~);同时,在学习这两个概念的过程中,我们隐隐约约把握到了 Observer,在 RxJS 中并没有一个专门的类叫 Observer,因此,我们会在后续的学习中不断加深理解。

下一步?

按照正常的思路,我们可能会去关注 pipe() 函数内部各种神奇的 operators,学习每一个操作符的实现原理。但很明显,我们不走寻常路,其实关于 Observable 的路我们还远远没有走完,接下来,我们来关注一个可能比较有意思的玩意儿 —— Subjects;而 RxJS 源代码中有关 Observable 的对象包括 ConnectableObservable,GroupedObservable 等,我们在学习操作符时再来关注。

以上!