Rxjs - operators

134 阅读5分钟

RxJS 对于它的运算符来说最有用,即使Observable是基础。运算符是允许以声明方式轻松组合复杂异步代码的基本部分。

什么是运算符?

运算符是函数,有两种运算符:

Pipeable Operators是那种可以使用语法ObservableInstance.pipe(operator)或者更常见的 ObservableInstance.pipe(operatorFactory())通过管道传送到Observables的操作符。运算符工厂函数包括 filter(...)和mergeMap(...)。

当调用 Pipeable Operators 时,它们不会更改现有的 Observable 实例。相反,它们返回一个新的 Observable,它的订阅逻辑基于第一个 Observable。

Pipeable Operator是一个函数,它接受一个Observable作为它的输入,并返回另一个Observable。这是一个纯操作: 先前的 Observable 保持未修改。

Pipeable Operator Factory 是一个函数,它可以接受参数来设置上下文并返回一个 Pipeable Operator。工厂的参数属于操作符的词法范围。

一个 Pipeable Operator本质上是一个纯函数,它以一个Observable作为输入,并生成另一个Observable作为输出。订阅输出Observable也将订阅输入Observable。

Creation Operators 是另一种类型的运算符,它可以被称为独立函数,以创建一个新的Observable。例如: of(1,2,3)创建一个Observable,将发出1,2和3,一个接一个。创建操作符将在后面的小节中进行更详细的讨论。

例如,名为 map 的运算符类似于同名的 Array 方法。就像[1,2,3].Map (x = > x * x)将产生[1,4,9],可观测的结果如下所示:

import { of, map } 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:

import { of, first } from 'rxjs';

of(1, 2, 3)
  .pipe(first())
  .subscribe((v) => console.log(`value: ${v}`));

// Logs:
// value: 1

注意,map在逻辑上必须动态构造,因为必须将映射函数提供给。相比之下,first 可以是一个常量,但仍然是在运行中构造的。作为一般实践,所有运算符都是构造的,无论它们是否需要参数。

Piping

Pipeable Operators是函数,所以它们可以像普通函数一样使用: op()(obs)——但实际上,它们往往有许多卷积在一起,很快就变得不可读: op4()(op3()(op2()(op1()(obs))))。由于这个原因,Observables有一个名为pipe()方法 ,可以完成同样的事情,同时更容易阅读。

obs.pipe(op1(), op2(), op3(), op4());

作为一个风格问题,op()(obs)从不使用,即使只有一个操作符; obs.pipe(op())是普遍首选的。

创建操作符

什么是创建操作符?与Pipeable Operators不同,创建操作符是一些函数,可用于创建具有某些通用预定义行为的Observable,或通过连接其他Observables来创建Observable。

创建运算符的一个典型例子是interval函数。它接受一个数字(不是一个Observable)作为输入参数,并生成一个Observable作为输出:

import { interval } from 'rxjs';

const observable = interval(1000 /* number of milliseconds */);

高阶Observable

Observables通常发出普通的值,如字符串和数字,但令人惊讶的是,经常需要处理 Observables 的Observables,即所谓的高阶可观测数据。例如,假设您有一个Observable发出字符串,它是您想要查看的文件的 URL。代码可能是这样的:

const fileObservable = urlObservable.pipe(map((url) => http.get(url)));

http.get()为每个单独的 URL 返回一个Observable(可能是字符串或字符串数组)。现在你有一个 Observables的Observable,一个高阶Observable。

但是如何处理高阶Observable呢?通常,通过扁平化: 通过(以某种方式)将高阶Observable转换为普通Observable。例如:

const fileObservable = urlObservable.pipe(
  map((url) => http.get(url)),
  concatAll()
);

ConcatAll()操作符订阅每个从“外部”Observable出来的“内部”Observable,并复制所有发出的值,直到该Observable完成,并继续到下一个。所有的值都以这种方式连接。
其他有用的扁平化运算符(称为连接运算符)包括:

  • mergeAll() —— 在每个内部Observable到达时订阅它,然后在它到达时发出每个值
  • switchAll() —— 在第一个内部Observable到达时订阅它,并在它到达时发出每个值,但是当下一个内部Observable到达时,取消订阅前一个内部Observable,并订阅新的内部Observable。
  • exhaustAll() —— 当第一个内部Observable到达时订阅它,并在它到达时发出每个值,丢弃所有新到达的内部Observable,直到第一个完成,然后等待下一个内部Observable。

正如许多数组库将 map()flat()(或 flatten())组合为单个 flatMap()一样,也存在所有 RxJS 扁平化操作符 concatMap()mergeMap()switchMap()exhaustMap()的映射等价物。

弹珠图(Marble diagrams)

要解释操作符是如何工作的,文本描述往往是不够的。许多运算符与时间有关,例如,它们可能以不同的方式延迟、采样、节流或去除值排放。图表通常是更好的工具。弹珠图是运算符工作方式的可视化表示,包括输入Observable(s)、运算符及其参数和输出Observable。

在弹珠图中,时间流向右边,该图描述了值(“弹珠”)是如何在Observable执行中发出的。

image.png

在这个文档站点中,我们广泛地使用弹珠图表来解释操作符是如何工作的。它们在其他环境中也可能非常有用,比如在白板上,甚至在我们的单元测试中(如 ASCII 图)。