什么是 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
方法接受三个可选参数,结合文首所使用的示例:显然,我们并未传入error
和complete
参数,参数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.error
和 subscriber.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
部分的内容直接跳过了。接下来,我们将重点学习 Subscriber
和 Subscription
的相关内容,借此将 Observable 这一个核心概念彻底吃透。再回过头来,了解有关 Observable 更为复杂的内容。
以上!