阅读 200

RxJS 源代码学习(二)—— Observable

什么是 Observable

概念

对于 Observable 的定义,RxJS 官方文档的描述是:

Observables are lazy Push collections of multiple values.

也不知道在说些什么,不过没关系,我们可以一个一个看:首先最让人迷惑的概念是 Push,官方文档的解释是:

In Push systems, the Producer determines when to send data to the Consumer. The Consumer is unaware of when it will receive that data.

在 JavaScript 中,Promise 便是一个典型的推送系统,我们并不知道 Promise 对象何时会返回我们所需要(resovled)的值,只能通过注册回调函数来等待这个「未来值」。Observable也是这么一个推送系统。与之相对的,自然是 Pull 了:

In Pull systems, the Consumer determines when it receives data from the data Producer. The Producer itself is unaware of when the data will be delivered to the Consumer.

在 JavaScript 中,每一个函数都是一个 Pull 系统。再来看 lazy,虽然 Observable是一个 Push 系统,但并不意味着它会「积极而主动」地将数据推送到我们手上;只有我们订阅 Observable,我们才可以拿到我们需要的数据。举个例子:

import { Observable } from 'rxjs';

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

// Subscribe the lazy Push System
observable.subscribe({
    next: (value: number) => console.log('we got', value);
    error: (error: any) => console.error(error);
    complete: () => console.log('completed');
});

/**
 * Output:
 * we got 1
 * we got 2
 * we got 3
 * completed
 */
复制代码

原理

熟悉订阅者模式的同学,亦会对上述代码感到十分熟悉。当然,我们的主题不是讲解订阅者模式,而是学习 RxJS 的源代码。那么,RxJS 是如何实现上述逻辑的?起初,我们通过 new 运算符创建了一个对象实例Observable,其源代码构造函数如下所示:

export class Observable<T> implements Subscribable<T> {
    /** ... */
    
    constructor(subscribe?: (this: Observable<T>, subscriber: Subscriber<T>) => TeardownLogic) {
        if (subscribe) {
            this._subscribe = subscribe;
        }
    }

    protected _subscribe(subscriber: Subscriber<any>): TeardownLogic {
        return this.source?.subscribe(subscriber);
    }

    /** ... */
}
复制代码

如上所示,当使用运算符 new 实例化 Observable 对象,其传入参数对内部方法_subscribe进行了重写。若是实例化对象时,传入空参数,由于内部source属性初始值为undefined,该内部方法也总是返回undefined。因此,假设我们可以看到变量observable内部的值,则:

observable._subscribe = (subsciber: Subscriber<number>) => { 
    subscriber.next(1);
    subscriber.next(2);
    subscriber.next(3);
    subscriber.complete();
};
复制代码

我们先不着急了解 Subscriber 的实现原理,继续学习订阅 Observable 对象的实现,其源代码如下所示:

export class Observable<T> implements Subscribable<T> {
    /** ... */
    constructor(subscribe?: (this: Observable<T>, subscriber: Subscriber<T>) => TeardownLogic) {
        if (subscribe) {
            this._subscribe = subscribe;
        }
    }

    protected _subscribe(subscriber: Subscriber<any>): TeardownLogic {
        return this.source?.subscribe(subscriber);
    }

    subscribe(
        observerOrNext?: Partial<Observer<T>> | ((value: T) => void) | null,
        error?: ((error: any) => void) | null,
        complete?: (() => void) | null
    ): Subscription {
        const subscriber = isSubscriber(observerOrNext) ? observerOrNext : new SafeSubscriber(observerOrNext, error, complete);
        if (config.useDeprecatedSynchronousErrorHandling) {
            this._deprecatedSyncErrorSubscribe(subscriber);
        } else {
            const { operator, source } = this;
            subscriber.add(
                operator
                    ? // We're dealing with a subscription in the
                      // operator chain to one of our lifted operators.
                      operator.call(subscriber, source)
                    : source
                    ? // If `source` has a value, but `operator` does not, something that
                      // had intimate knowledge of our API, like our `Subject`, must have
                      // set it. We're going to just call `_subscribe` directly.
                      this._subscribe(subscriber)
                    : // In all other cases, we're likely wrapping a user-provided initializer
                      // function, so we need to catch errors and handle them appropriately.
                      this._trySubscribe(subscriber)
                );
        }
        return subscriber;
    }
    /** ... */
}
复制代码

Observable 对象的subscibe方法接受三个可选参数,结合文首所使用的示例:显然,我们并未传入errorcomplete参数,参数observerOrNext存在三种类型,包括Observer<T>对象,函数(value: T) => void和空值null。对于Observer<T>,RxJS 的接口定义是:

export interface Observer<T> {
    next: (value: T) => void;
    error: (err: any) => void;
    complete: () => void;
}
复制代码

由此可见,我们传入的参数即为一个Observer<T>对象。代码是如何判断的呢?首先,代码会通过函数isSubcriber判断是否一个Subscriber实例:

function isSubscriber<T>(value: any): value is Subscriber<T> {
    return (value && value instanceof Subscriber) || (isObserver(value) && isSubscription(value));
}

function isObserver<T>(value: any): value is Observer<T> {
    return value && isFunction(value.next) && isFunction(value.error) && isFunction(value.complete);
}

function isSubscription(value: any): value is Subscription {
    return (
        value instanceof Subscription ||
        (value && 'closed' in value && isFunction(value.remove) && isFunction(value.add) && isFunction(value.unsubscribe))
    );
}
复制代码

显然,单纯一个仅包含 next、error 和 complete 函数的对象并非一个 Subscriber<T> 对象,RxJS 会以此为基础创建一个 SafeSubscriber对象,并以此调用_trySubscribe方法,本质上亦是调用this._subscribe方法:

protected _trySubscribe(sink: Subscriber<T>): TeardownLogic {
    try {
        return this._subscribe(sink);
    } catch (err) {
        // We don't need to return anything in this case,
        // because it's just going to try to `add()` to a subscription
        // above.
        sink.error(err);
    }
}
复制代码

前文提到,this._subscribe在对象实例化时,便已经被重写为传入的参数,因此,我们创建并订阅变量 observable 的本质,如下所示:

// pseudo code

// 创建 Observable
observable._subscribe = (subsciber: Subscriber<number>) => { 
    subscriber.next(1);
    subscriber.next(2);
    subscriber.next(3);
    subscriber.complete();
};

// 创建 Subscriber
subscriber = {
    next: (value: number) => console.log('we got', value);
    error: (error: any) => console.error(error);
    complete: () => console.log('completed');
}

// 订阅 Observable
observable._subscribe(subscriber)
复制代码

满满的订阅者模式的即视感!关于订阅者的内容,我们先了解到这,后续学习订阅者相关内容时,我们再深入学习;接下来依旧要回归主题,关于 Observable 的一切!

Observable 还有什么

.pipe()

熟练使用 RxJS 进行日常编程的同学一定不会对 pipe() 感到陌生。我最初了解到有关 pipe 的概念是在 Linux 编程中的管道函数:

管道是一种把两个进程之间的标准输入和标准输出连接起来的机制,从而提供一种让多个进程间通信的方法。

在 JavaScript 中,函数式编程(Functional Programming)吸收了这一思想,通过一个 pipe 函数将函数组合起来,上一个函数的输出成为下一个函数的输入参数,其一般实现形式基本如下所示:

const pipe = ...args => x => 
  args.reduce(
    (outputValue, currentFunction) => currentFunction(outputValue),
    x
  );
复制代码

RxJS 源代码对于 pipe() 的处理更加简明:

export class Observable<T> implements Subscribable<T> {
    /** ... */
    pipe(...operations: OperatorFunction<any, any>[]): Observable<any> {
        return operations.length ? pipeFromArray(operations)(this) : this;
    }
    /** ... */
}

function identity<T>(x: T): T {
    return x;
}

function pipeFromArray<T, R>(fns: Array<UnaryFunction<T, R>>): UnaryFunction<T, R> {
  if (fns.length === 0) {
    return identity as UnaryFunction<any, any>;
  }

  if (fns.length === 1) {
    return fns[0];
  }

  return function piped(input: T): R {
    return fns.reduce((prev: any, fn: UnaryFunction<T, R>) => fn(prev), input as any);
  };
}
复制代码

我们一层一层拨开来看,首先 pipe() 函数需要传入 0 到 N 个操作符函数,根据参数数量逐一执行或直接返回 Observable 对象;再来看pipeFromArray<T, R>函数,其接受一个由单元函数组成的数组作为参数,单元函数的接口如下所示:

export interface UnaryFunction<T, R> {
  (source: T): R;
}
复制代码

其表示接受一个类型为 T 的参数,输出一个类型为 R 的值,颇有几分 Scala 的味道,可以说很函数式了。若参数 fns 的为空,则返回一个 identity 函数,使得 pipe 函数直接返回 Observable 对象;若仅包含一个元素,则直接返回该元素;否则,则返回一个真正意义上的 pipe 函数,其形式基本与上文所写的函数式编程中的 pipe 函数并无二致。

.forEach()

有人认为 forEach 方法是 subscribe 方法的简化,结合源代码来看,是有道理的。直接来看源代码:

export class Observable<T> implements Subscribable<T> {
    /** ... */
    forEach(next: (value: T) => void, promiseCtor?: PromiseConstructorLike): Promise<void> {
        promiseCtor = getPromiseCtor(promiseCtor);
        return new promiseCtor<void>((resolve, reject) => {
            // Must be declared in a separate statement to avoid a ReferenceError when
            // accessing subscription below in the closure due to Temporal Dead Zone.
            let subscription: Subscription;

            subscription = this.subscribe(
                (value) => {
                    try {
                        next(value);
                    } catch (err) {
                        reject(err);
                        subscription?.unsubscribe();
                    }
                },
                reject,
                resolve
            );
        }) as Promise<void>;
    }
    /*... */
}
复制代码

此处,我们并不需要过多关注 getPromiseCtor 对象,直接将其理解为 JavaScript 中的 Promise 对象。可见,forEach 方法实则返回了一个 Promise 对象,并在内部对 Observable 对象进行订阅,并分别用 reject 函数和 resolve 函数处理 subscriber.errorsubscriber.complete的情况。可见,当我们明确知道所要处理的 Observable 对象内部将会包含哪些值,及其结束 Push 的时机,并需要在所有内部值 Push 完毕之后再作相应处理时,我们可以直接调用 forEach 方法进行处置。由此可见,forEach 方法是 subscribe 方法的简化这一看法,确实不无道理。

.toPromise()

由上可知,forEach 实质上将一个 Observable 对象转换成了一个 Promise 对象,直接处理 Observable 对象内部值推送完毕的结果;另有一个 toPromise 方法也是同样将一个 Observable 对象转换成了一个 Promise 对象,二者的源代码亦十分相似:

export class Observable<T> implements Subscribable<T> {
    /** ... */
    toPromise(promiseCtor?: PromiseConstructorLike): Promise<T | undefined> {
        promiseCtor = getPromiseCtor(promiseCtor);
        return new promiseCtor((resolve, reject) => {
            let value: T | undefined;
            this.subscribe(
                (x: T) => (value = x),
                (err: any) => reject(err),
                () => resolve(value)
            );
        }) as Promise<T | undefined>;
    }
    /*... */
}
复制代码

值得一提的是,该方法在 RxJS 5.5+ 版本中已经被废弃,官方推荐使用另外两个函数来替代它,包括函数 firstValueFrom 和函数 lastValueFrom,二者的源代码我们将在后续深入学习。

下一步?

关于 RxJS 中 Observable 对象我们大体已经学习完毕,不过细心的同学一定发现了,前文有关 Subscriber 部分的内容直接跳过了。接下来,我们将重点学习 SubscriberSubscription 的相关内容,借此将 Observable 这一个核心概念彻底吃透。再回过头来,了解有关 Observable 更为复杂的内容。

以上!

文章分类
前端
文章标签