RxJS(Reactive Extensions for JavaScript)是一个用于处理异步数据流的库,它基于观察者模式和迭代器模式,提供了强大的工具来处理事件、异步操作和数据流。RxJS 的核心概念是 Observable(可观察对象),它代表一个数据流,可以被订阅(subscribe)以接收数据。
官网介绍: 可以把 RxJS 当做是用来处理事件的 Lodash 。
基础概念
在 RxJS 中用来解决异步事件管理的的基本概念是:
- Observable (可观察对象): 表示一个概念,这个概念是一个可调用的未来值或事件的集合。
- Observer (观察者): 一个回调函数的集合,它知道如何去监听由 Observable 提供的值。
- Subscription (订阅): 表示 Observable 的执行,主要用于取消 Observable 的执行。
- Operators (操作符): 采用函数式编程风格的纯函数 (pure function),使用像
map
、filter
、concat
、flatMap
等这样的操作符来处理集合。 - Subject (主体): 相当于 EventEmitter,并且是将值或事件多路推送给多个 Observer 的唯一方式。
- Schedulers (调度器): 用来控制并发并且是中央集权的调度员,允许我们在发生计算时进行协调,例如
setTimeout
或requestAnimationFrame
或其他。
Observable 剖析
Observables 是使用 Rx.Observable.create
或创建操作符创建的 ,并使用观察者来订阅 它,然后执行 它并发送 next
/ error
/ complete
通知给观察者,而且执行可能会被清理。这四个方面全部编码在 Observables 实例中,但某些方面是与其他类型相关的,像 Observer (观察者) 和 Subscription (订阅)。
Observable 的核心关注点:
- 创建 Observables
- 订阅 Observables
- 执行 Observables
- 清理 Observables
通常我们使用所谓的创建操作符, 像 of
、from
、interval
、等等
订阅 Observables
订阅 Observable 像是调用函数, 并提供接收数据的回调函数。可以传入一个函数或者是对象
tsx
var observer = {
next: x => console.log('Observer got a next value: ' + x),
error: err => console.error('Observer got an error: ' + err),
complete: () => console.log('Observer got a complete notification'),
};
observable.subscribe(observer);
// or
observable.subscribe(x => console.log('Observer got a next value: ' + x));
// or next,error,complete
observable.subscribe(
x => console.log('Observer got a next value: ' + x),
err => console.error('Observer got an error: ' + err),
() => console.log('Observer got a complete notification')
);
执行 Observables
Observable 执行可以传递三种类型的值:
- "Next" 通知: 发送一个值,比如数字、字符串、对象,等等。
- "Error" 通知: 发送一个 JavaScript 错误 或 异常。
- "Complete" 通知: 不再发送任何值。
tsx
var observable = Rx.Observable.create(function subscribe(observer) {
try {
observer.next(1);
observer.next(2);
observer.next(3);
observer.complete();
} catch (err) {
observer.error(err); // 如果捕获到异常会发送一个错误
}
});
var subscription = observable.subscribe(x => console.log(x));
// 删除
subscription.unsubscribe();
当你订阅了 Observable,你会得到一个 Subscription ,它表示进行中的执行。只要调用 unsubscribe()
方法就可以取消执行。
在内部提供 unsubscribe 函数
tsx
var observable = Rx.Observable.create(function subscribe(observer) {
// 追踪 interval 资源
var intervalID = setInterval(() => {
observer.next('hi');
}, 1000);
// 提供取消和清理 interval 资源的方法
return function unsubscribe() {
clearInterval(intervalID);
};
});
Subscription (订阅)
Subscription 基本上只有一个 unsubscribe()
函数,这个函数用来释放资源或去取消 Observable 执行。
tsx
var observable = Rx.Observable.interval(1000);
var subscription = observable.subscribe(x => console.log(x));
// 稍后:
// 这会取消正在进行中的 Observable 执行
// Observable 执行是通过使用观察者调用 subscribe 方法启动的
subscription.unsubscribe();
Subscription 还可以合在一起,这样一个 Subscription 调用 unsubscribe()
方法,可能会有多个 Subscription 取消订阅 。
Subscriptions 还有一个 remove(otherSubscription)
方法,用来撤销一个已添加的子 Subscription
tsx
var observable1 = Rx.Observable.interval(400);
var observable2 = Rx.Observable.interval(300);
var subscription = observable1.subscribe(x => console.log('first: ' + x));
var childSubscription = observable2.subscribe(x => console.log('second: ' + x));
subscription.add(childSubscription);
setTimeout(() => {
// subscription 和 childSubscription 都会取消订阅
subscription.unsubscribe();
}, 1000);
Subject (主体)
RxJS Subject
是一种特殊类型的 Observable
,它允许将值多播给多个观察者,所以 Subject
是多播的,而普通的 Observables
是单播的(每个已订阅的观察者都拥有 Observable
的独立执行)。
每个 Subject 都是 Observable 。也可以每个 Subject 都是观察者。
观察者
tsx
import { Subject } from "rxjs";
var subject = new Subject();
subject.subscribe({
next: (v) => console.log("observerA: " + v),
});
subject.subscribe({
next: (v) => console.log("observerB: " + v),
});
subject.next(1);
subject.next(2);
// observerA: 1
// observerB: 1
// observerA: 2
// observerB: 2
当做 Observable
tsx
import { from, Subject } from "rxjs";
var subject = new Subject();
subject.subscribe({
next: (v) => console.log("observerA: " + v),
});
subject.subscribe({
next: (v) => console.log("observerB: " + v),
});
var observable = from([1, 2, 3]);
observable.subscribe(subject); // 你可以提供一个 Subject 进行订阅
// observerA: 1
// observerB: 1
// observerA: 2
// observerB: 2
// observerA: 3
// observerB: 3
还有一些特殊类型的 Subject
:BehaviorSubject
、ReplaySubject
和 AsyncSubject
。
BehaviorSubject
Subject 的其中一个变体就是 BehaviorSubject
,它有一个"当前值"的概念。它保存了发送给消费者的最新值。并且当有新的观察者订阅时,会立即从 BehaviorSubject
那接收到"当前值",多个订阅拿到的是最新的值,可以当做管道处理。
tsx
import { BehaviorSubject } from "rxjs";
const subject = new BehaviorSubject(0); // 0 is the initial value
subject.subscribe({
next: (v) => console.log(`observerA: ${v}`),
});
subject.next(1);
subject.next(2);
subject.subscribe({
next: (v) => console.log(`observerB: ${v}`),
});
subject.next(3);
// Logs
// observerA: 0
// observerA: 1
// observerA: 2
// observerB: 2
// observerA: 3
// observerB: 3
ReplaySubject
ReplaySubject
类似于 BehaviorSubject
,发送旧值给新的订阅者,但它还可以记录 Observable 执行的一部分。
tsx
import { ReplaySubject } from "rxjs";
// 缓存3个值在缓冲区,有新的订阅就发送,最新的3个
const subject = new ReplaySubject(3); // buffer 3 values for new subscribers
subject.subscribe({
next: (v) => console.log(`observerA: ${v}`),
});
subject.next(1);
subject.next(2);
subject.next(3);
subject.next(4);
subject.subscribe({
next: (v) => console.log(`observerB: ${v}`),
});
subject.next(5);
// Logs:
// observerA: 1
// observerA: 2
// observerA: 3
// observerA: 4
// observerB: 2
// observerB: 3
// observerB: 4
// observerA: 5
// observerB: 5
AsyncSubject
AsyncSubject 是另一个 Subject 变体,只有当 Observable 执行完成时 (执行 complete()
),它才会将执行的最后一个值发送给观察者。
tsx
import { AsyncSubject } from "rxjs";
const subject = new AsyncSubject();
subject.subscribe({
next: (v) => console.log(`observerA: ${v}`),
});
subject.next(1);
subject.next(2);
subject.next(3);
subject.next(4);
subject.subscribe({
next: (v) => console.log(`observerB: ${v}`),
});
subject.next(5);
subject.complete();
// Logs:
// observerA: 5
// observerB: 5
Operators (操作符)
操作符是 Observable 类型上的方法 ,比如 .map(...)
、.filter(...)
、.merge(...)
,等等。当操作符被调用时,它们不会改变 已经存在的 Observable 实例。相反,它们返回一个新的 Observable ,它的 subscription 逻辑基于第一个 Observable 。
操作符是函数,它基于当前的 Observable 创建一个新的 Observable。这是一个无副作用的操作:前面的 Observable 保持不变。
tsx
import { of, map, first } from 'rxjs';
of(1, 2, 3)
.pipe(map((x) => x * x))
.subscribe((v) => console.log(`value: ${v}`));
// Logs:
// value: 1
// value: 4
// value: 9
// first 操作符
of(1, 2, 3)
.pipe(first())
.subscribe((v) => console.log(`value: ${v}`));
// Logs:
// value: 1
Piping 管道
管道操作符是函数,因此可以像普通函数一样使用
tsx
obs.pipe(op1(), op2(), op3(), op4());
常用操作符
map、filter、mergeMap、of、zip、first ...
Scheduler 调度器
调度器控制着何时启动 subscription 和何时发送通知。它由三部分组成:
- 调度器是一种数据结构。 它知道如何根据优先级或其他标准来存储任务和将任务进行排序。
- 调度器是执行上下文。 它表示在何时何地执行任务(举例来说,立即的,或另一种回调函数机制(比如 setTimeout 或 process.nextTick),或动画帧)。
- 调度器有一个(虚拟的)时钟。 调度器功能通过它的 getter 方法
now()
提供了"时间"的概念。在具体调度器上安排的任务将严格遵循该时钟所表示的时间。
把任务全部转换为异步
tsx
import { Observable, observeOn, asyncScheduler } from "rxjs";
const observable = new Observable((observer) => {
observer.next(1);
observer.next(2);
observer.next(3);
observer.complete();
}).pipe(observeOn(asyncScheduler));
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");
// just before subscribe
// just after subscribe
// got value 1
// got value 2
// got value 3
// done
observeOn(asyncScheduler)
在 new Observable
和最终观察者之间引入了一个代理观察者。
proxyObserver
创建于 observeOn(asyncScheduler)
,其 next(val)
功能大致如下:
tsx
const proxyObserver = {
next(val) {
asyncScheduler.schedule(
(x) => finalObserver.next(x),
0 /* delay */,
val /* 第一个参数的函数实参值 */
);
},
// ...
};
async
调度器操作符使用了 setTimeout
或 setInterval
,即使给定的延迟时间
为0。照例,在 JavaScript 中,我们已知的是 setTimeout(fn, 0)
会在下一次事件循环迭代的最开始运行 fn
。
Scheduler Types 调度程序类型
Scheduler ****调度程序 | Purpose ****目的 |
---|---|
null |
不传递任何调度器的话,会以同步递归的方式发送通知。用于定时操作或尾递归操作。 |
queueScheduler |
当前事件帧中的队列调度。用于迭代操作。 |
asapScheduler |
微任务的队列调度,它使用可用的最快速的传输机制,比如 Node.js 的 process.nextTick() 或 Web Worker 的 MessageChannel 或 setTimeout 或其他。用于异步转换。 |
asyncScheduler |
使用 setInterval 的调度。用于基于时间的操作符。 |
animationFrameScheduler |
安排在下次浏览器内容重绘之前执行的任务。可用于创建流畅的浏览器动画。 |
使用调度器
静态创建操作符通常可以接收调度器作为参数。 举例来说,from(array, scheduler)
可以让你指定调度器,当发送从 array
转换的每个通知的时候使用。调度器通常作为操作符的最后一个参数。下面的静态创建操作符接收调度器参数:
bindCallback
bindNodeCallback
combineLatest
concat
empty
from
fromPromise
interval
merge
of
range
throw
timer
使用 subscribeOn
来安排在什么上下文中 subscribe()
调用会发生。默认情况下,在 Observable 上的 subscribe()
调用会同步且立即发生。然而,您可以使用实例操作符 subscribeOn(scheduler)
来延迟或安排实际订阅在给定的调度器上发生,其中 scheduler
是您提供的参数。
示例
- 简单的事件注册和监听
tsx
var button = document.querySelector('button');
button.addEventListener('click', () => console.log('Clicked!'));
// rxjs 的写法
var button = document.querySelector('button');
Rx.Observable.fromEvent(button, 'click')
.subscribe(() => console.log('Clicked!'));
- 发送多个值,包含同步和异步
tsx
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.subscribe({
next: (value) => console.log(value),
complete: () => console.log("Complete"),
error: (err) => console.error(err),
});
// 1
// 2
// 3
// 等待 1000ms
// 4
// Complete
Observer
Observer 是一个包含 next
、error
和 complete
方法的对象,用于处理 Observable 发出的值、错误和完成通知。
Subscription
Subscription 表示 Observable 的执行,可以通过 unsubscribe
方法来取消订阅。
- 使用操作符处理
tsx
import { of } from "rxjs";
import { map, filter } from "rxjs/operators";
const numbers = of(1, 2, 3, 4, 5);
const squaredNumbers = numbers.pipe(
filter((x) => x % 2 === 0),
map((x) => x * x)
);
squaredNumbers.subscribe((x) => console.log(x));
先试用 of
创建一个 Observable
,然后使用 pipe
管道处理值。