RxJS 介绍

RxJS(Reactive Extensions for JavaScript)是一个用于处理异步数据流的库,它基于观察者模式和迭代器模式,提供了强大的工具来处理事件、异步操作和数据流。RxJS 的核心概念是 Observable(可观察对象),它代表一个数据流,可以被订阅(subscribe)以接收数据。

官网介绍: 可以把 RxJS 当做是用来处理事件的 Lodash

基础概念

在 RxJS 中用来解决异步事件管理的的基本概念是:

  • Observable (可观察对象): 表示一个概念,这个概念是一个可调用的未来值或事件的集合。
  • Observer (观察者): 一个回调函数的集合,它知道如何去监听由 Observable 提供的值。
  • Subscription (订阅): 表示 Observable 的执行,主要用于取消 Observable 的执行。
  • Operators (操作符): 采用函数式编程风格的纯函数 (pure function),使用像 mapfilterconcatflatMap 等这样的操作符来处理集合。
  • Subject (主体): 相当于 EventEmitter,并且是将值或事件多路推送给多个 Observer 的唯一方式。
  • Schedulers (调度器): 用来控制并发并且是中央集权的调度员,允许我们在发生计算时进行协调,例如 setTimeoutrequestAnimationFrame 或其他。

Observable 剖析

Observables 是使用 Rx.Observable.create 或创建操作符创建的 ,并使用观察者来订阅 它,然后执行 它并发送 next / error / complete 通知给观察者,而且执行可能会被清理。这四个方面全部编码在 Observables 实例中,但某些方面是与其他类型相关的,像 Observer (观察者) 和 Subscription (订阅)。

Observable 的核心关注点:

  • 创建 Observables
  • 订阅 Observables
  • 执行 Observables
  • 清理 Observables

通常我们使用所谓的创建操作符, 像 offrominterval、等等

订阅 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

还有一些特殊类型的 SubjectBehaviorSubjectReplaySubjectAsyncSubject

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 调度器操作符使用了 setTimeoutsetInterval,即使给定的延迟时间为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 是您提供的参数。

示例

  1. 简单的事件注册和监听
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!'));
  1. 发送多个值,包含同步和异步
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 是一个包含 nexterrorcomplete 方法的对象,用于处理 Observable 发出的值、错误和完成通知。

Subscription

Subscription 表示 Observable 的执行,可以通过 unsubscribe 方法来取消订阅。

  1. 使用操作符处理
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 管道处理值。

相关推荐
秋刀鱼不做梦2 小时前
前端小案例——网页井字棋
前端·javascript·css·学习·html
用户8381286103302 小时前
探索Google AlloyDB for PostgreSQL:实现聊天消息历史存储
前端
用户0267001994942 小时前
[深入了解Google Trends API的使用与优化指南]
前端
hunter2062062 小时前
linux通过web向mac远程传输字符串,mac收到后在终端中直接打印。
linux·前端·macos
XinShun3 小时前
sqlalchemy The transaction is active - has not been committed or rolled back.
前端·数据库·python
tangtang1234 小时前
探索 Google BigQuery Vector Search:大规模语义搜索和嵌入式管理
前端
aricvvang4 小时前
前端学习笔记 - 布局方式总结
前端·css·html
沐沐霸5 小时前
day25_HTML
前端·html
MickeyCV5 小时前
Web前端开发技术之HTML&CSS知识点总结
前端·css·html