什么是 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 等,我们在学习操作符时再来关注。
以上!