Observable

184 阅读8分钟

Observable

Observable 是多个值的惰性推送集合。他们填补了下表中的缺失位置:

| |SINGLE | MULTIPLE |

| --- | --- | --- |

| Pull | Function | Function |

| Push | Promise | Observable|

例子:下面是一个 Observable,它在订阅时立即(同步)推送值 1、2、3,并且在 subscribe 调用后经过一秒后推送值 4,然后完成:


import { Observable } from 'rxjs';

const observable = new Observable(subscriber => {

subscriber.next(1);

subscriber.next(2);

subscriber.next(3);

setTimeout(() => {

subscriber.next(4);

subscriber.complete();

}, 1000);

});

要调用 Observable 并查看这些值,我们需要订阅它:


import { Observable } from 'rxjs';

const observable = new Observable(subscriber => {

subscriber.next(1);

subscriber.next(2);

subscriber.next(3);

setTimeout(() => {

subscriber.next(4);

subscriber.complete();

}, 1000);

});

console.log('just before subscribe');

observable.subscribe({

next(x) { console.log('got value ' + x); },

error(err) { console.error('something wrong occurred: ' + err); },

complete() { console.log('done'); }

});

console.log('just after subscribe');

Which executes as such on the console:


just before subscribe

got value 1

got value 2

got value 3

just after subscribe

got value 4

done

Pull versus Push

Pull 和 Push 是两种不同的协议,它们描述了数据生产者如何与数据消费者进行通信。

什么是拉?在 Pull 系统中,消费者决定何时从数据生产者接收数据。生产者本身不知道数据何时会传送给消费者。

每个 JavaScript 函数都是一个 Pull 系统。该函数是数据的生产者,调用该函数的代码通过从其调用中“拉出”单个返回值来消耗它

ES2015 引入了生成器函数和迭代器(函数*),另一种类型的拉系统。调用 iterator.next() 的代码是消费者,从迭代器(生产者)“拉”出多个值。

| | PRODUCER | CONSUMER |

| --- | --- | --- |

| Pull | Passive: produces data when requested. |Active: decides when data is requested. |

| Push | Active: produces data at its own pace. | Passive: reacts to received data. |

什么是推送?在推送系统中,生产者决定何时向消费者发送数据。消费者不知道何时会收到该数据。

Promise 是当今 JavaScript 中最常见的推送系统类型。 Promise(生产者)向注册的回调(消费者)提供已解析的值,但与函数不同的是,Promise 负责准确确定何时将该值“推送”到回调。

RxJS 引入了 Observables,一个新的 JavaScript 推送系统。 Observable 是多个值的生产者,将它们“推送”给观察者(消费者)。

  • 函数是一种延迟计算的计算,它在调用时同步返回单个值。

  • 生成器是一种延迟计算的计算,它在迭代时同步返回零到(可能)无限值。

  • Promise 是一种可能(也可能不会)最终返回单个值的计算。

  • Observable 是一种惰性求值的计算,从它被调用时起,它可以同步或异步地将零返回到(潜在的)无限值。

Observables 作为函数的概括

与流行的说法相反,Observables 不像 EventEmitters,也不像多个值的 Promises。在某些情况下,Observables 可能像 EventEmitters 一样,即当它们使用 RxJS Subjects 多播时,但通常它们不像 EventEmitters。

Observables 就像带有零参数的函数,但将它们概括为允许多个值。

考虑以下:


function foo() {

console.log('Hello');

return 42;

}

const x = foo.call(); // same as foo()

console.log(x);

const y = foo.call(); // same as foo()

console.log(y);

我们希望看到输出:


"Hello"

42

"Hello"

42

您可以编写上述相同的行为,但使用 Observables:


import { Observable } from 'rxjs';

const foo = new Observable(subscriber => {

console.log('Hello');

subscriber.next(42);

});

foo.subscribe(x => {

console.log(x);

});

foo.subscribe(y => {

console.log(y);

});

输出是相同的:


"Hello"

42

"Hello"

42

发生这种情况是因为函数和 Observable 都是惰性计算。如果你不调用这个函数,console.log('Hello') 就不会发生。同样对于 Observables,如果你不“调用”它(订阅),console.log('Hello') 就不会发生。另外,“调用”或“订阅”是一个独立的操作:两个函数调用触发两个单独的副作用,两个 Observable 订阅触发两个单独的副作用。与 EventEmitters 共享副作用并且不管订阅者是否存在都急切执行不同,Observables 没有共享执行并且是惰性的。

订阅 Observable 类似于调用函数。

有些人声称 Observables 是异步的。那不是真的。如果你用日志包围一个函数调用,像这样:


console.log('before');

console.log(foo.call());

console.log('after');

您将看到输出:


"before"

"Hello"

42

"after"

这与 Observables 的行为相同:


console.log('before');

foo.subscribe(x => {

console.log(x);

});

console.log('after');

您将看到输出:


"before"

"Hello"

42

"after"

这证明 foo 的订阅是完全同步的,就像一个函数。

Observable 能够同步或异步地传递值。

Observable 和函数有什么区别?随着时间的推移,Observable 可以“返回”多个值,而函数则不能。你不能这样做:


function foo() {

console.log('Hello');

return 42;

return 100; // dead code. will never happen

}

函数只能返回一个值。然而,Observables 可以做到这一点:


import { Observable } from 'rxjs';

const foo = new Observable(subscriber => {

console.log('Hello');

subscriber.next(42);

subscriber.next(100); // "return" another value

subscriber.next(200); // "return" yet another

});

console.log('before');

foo.subscribe(x => {

console.log(x);

});

console.log('after');

带同步输出:


"before"

"Hello"

42

100

200

"after"

但您也可以异步“返回”值:


import { Observable } from 'rxjs';

const foo = new Observable(subscriber => {

console.log('Hello');

subscriber.next(42);

subscriber.next(100);

subscriber.next(200);

setTimeout(() => {

subscriber.next(300); // happens asynchronously

}, 1000);

});

console.log('before');

foo.subscribe(x => {

console.log(x);

});

console.log('after');

输出


"before"

"Hello"

42

100

200

"after"

300

结论:

  • func.call() 表示“同步给我一个值”

  • observable.subscribe() 意味着“给我任意数量的值,同步或异步”

Observable 的剖析

Observable 是使用新的 Observable 或创建运算符创建的,通过观察者订阅,执行以将下一个/错误/完成通知传递给观察者,并且它们的执行可能会被处置。这四个方面都编码在一个 Observable 实例中,但其中一些方面与其他类型相关,例如 Observer 和 Subscription。

核心 Observable 关注点:

  • 创建 Observable

  • 订阅 Observables

  • 执行 Observable

  • 处理 Observable

创建 Observable

Observable 构造函数接受一个参数:subscribe 函数。

下面的示例创建一个 Observable 以每秒向订阅者发送字符串 'hi'。


import { Observable } from 'rxjs';

const observable = new Observable(function subscribe(subscriber) {

const id = setInterval(() => {

subscriber.next('hi')

}, 1000);

});

可以使用新的 Observable 创建 Observable。最常见的是,可观察对象是使用创建函数创建的,例如 of、from、interval 等。

在上面的例子中,subscribe 函数是描述 Observable 的最重要的部分。让我们看看订阅是什么意思。

订阅 Observables

示例中的 Observable observable 可以订阅,如下所示:


observable.subscribe(x => console.log(x));

observable.subscribe 和 new Observable(function subscribe(subscriber) {...}) 中的 subscribe 同名并非巧合。在库中,它们是不同的,但出于实际目的,您可以认为它们在概念上是相同的。

这显示了如何在同一个 Observable 的多个观察者之间不共享订阅调用。当使用观察者调用 observable.subscribe 时,函数 subscribe in new Observable(function subscribe(subscriber) {...}) 为给定的订阅者运行。对 observable.subscribe 的每次调用都会为给定的订阅者触发它自己的独立设置。

订阅 Observable 就像调用一个函数,提供数据将被传递到的回调。

这与 addEventListener / removeEventListener 等事件处理程序 API 截然不同。使用 observable.subscribe 时,给定的 Observer 不会在 Observable 中注册为侦听器。 Observable 甚至不维护附加的观察者列表。

订阅调用只是一种启动“可观察执行”并将值或事件传递给该执行的观察者的方法。

执行 Observable

new Observable(function subscribe(subscriber) {...}) 中的代码代表了一个“Observable 执行”,这是一个惰性计算,它只发生在每个订阅的观察者身上。随着时间的推移,执行会同步或异步地产生多个值。

Observable Execution 可以提供三种类型的值:

  • “下一个”通知:发送一个值,如数字、字符串、对象等。

  • “错误”通知:发送 JavaScript 错误或异常。

  • “完成”通知:不发送值。

“下一个”通知是最重要和最常见的类型:它们代表传递给订阅者的实际数据。 “错误”和“完成”通知在 Observable 执行期间可能只发生一次,并且只能出现其中之一。

这些约束在所谓的 Observable Grammar 或 Contract 中表达得最好,写成正则表达式:


next*(error|complete)?

在 Observable Execution 中,可以传递零到无限的 Next 通知。如果发送了 Error 或 Complete 通知,则此后无法发送任何其他通知。

下面是一个 Observable 执行的例子,它传递三个 Next 通知,然后完成:


import { Observable } from 'rxjs';

const observable = new Observable(function subscribe(subscriber) {

subscriber.next(1);

subscriber.next(2);

subscriber.next(3);

subscriber.complete();

});

Observables 严格遵守 Observable Contract,因此以下代码不会传递 Next 通知 4:


import { Observable } from 'rxjs';

const observable = new Observable(function subscribe(subscriber) {

subscriber.next(1);

subscriber.next(2);

subscriber.next(3);

subscriber.complete();

subscriber.next(4); // 不发货,因为它会违反合同

});

使用 try/catch 块在 subscribe 中包装任何代码是一个好主意,如果它捕获异常,它将传递错误通知:\


import { Observable } from 'rxjs';

const observable = new Observable(function subscribe(subscriber) {

try {

subscriber.next(1);

subscriber.next(2);

subscriber.next(3);

subscriber.complete();

} catch (err) {

subscriber.error(err); // delivers an error if it caught one

}

});

处理可观察的执行

因为 Observable Executions 可能是无限的,并且 Observer 想要在有限的时间内中止执行是很常见的,我们需要一个 API 来取消执行。由于每次执行只对一个观察者独占,一旦观察者完成接收值,它必须有办法停止执行,以避免浪费计算能力或内存资源。

当 observable.subscribe 被调用时,Observer 被附加到新创建的 Observable 执行。此调用还返回一个对象,即订阅:


import { from } from 'rxjs';

const observable = from([10, 20, 30]);

const subscription = observable.subscribe(x => console.log(x));

// Later:

subscription.unsubscribe();

当你订阅时,你会得到一个 Subscription,它代表正在进行的执行。只需调用 unsubscribe() 即可取消执行。

当我们使用 create() 创建 Observable 时,每个 Observable 必须定义如何处置该执行的资源。您可以通过从函数 subscribe() 中返回自定义取消订阅函数来实现。


const observable = new Observable(function subscribe(subscriber) {

// Keep track of the interval resource

const intervalId = setInterval(() => {

subscriber.next('hi');

}, 1000);

// Provide a way of canceling and disposing the interval resource

return function unsubscribe() {

clearInterval(intervalId);

};

});

就像 observable.subscribe 类似于 new Observable(function subscribe() {...}) 一样,我们从 subscribe 返回的取消订阅在概念上等同于 subscription.unsubscribe。事实上,如果我们移除围绕这些概念的 ReactiveX 类型,我们就会得到相当简单的 JavaScript。


function subscribe(subscriber) {

const intervalId = setInterval(() => {

subscriber.next('hi');

}, 1000);

return function unsubscribe() {

clearInterval(intervalId);

};

}

const unsubscribe = subscribe({next: (x) => console.log(x)});

// Later:

unsubscribe(); // dispose the resources

我们使用 Observable、Observer 和 Subscription 等 Rx 类型的原因是为了获得安全性(例如 Observable Contract)和与 Operator 的可组合性。