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 的可组合性。