RxJS Operators
尽管 Observable 是基础,但 RxJS 对其操作符最有用。运算符是允许以声明方式轻松组合复杂异步代码的基本部分。
What are operators?
运算符是函数。有两种运算符:
Pipeable Operators 是一种可以使用 observableInstance.pipe(operator()) 语法通过管道传输到 Observables 的操作符。其中包括 filter(...) 和 mergeMap(...)。调用时,它们不会更改现有的 Observable 实例。相反,它们返回一个新的 Observable,其订阅逻辑基于第一个 Observable。
Pipeable Operator 是一个将 Observable 作为其输入并返回另一个 Observable 的函数。这是一个纯粹的操作:之前的 Observable 保持不变。
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] 一样,Observable 是这样创建的:
import { of } from 'rxjs';
import { map } from 'rxjs/operators';
of(1, 2, 3)
.pipe(map((x) => x * x))
.subscribe((v) => console.log(`value: ${v}`));
// Logs:
// value: 1
// value: 4
// value: 9
将发出 1、4、9。另一个有用的运算符是 first:
import { of } from 'rxjs';
import { first } from 'rxjs/operators';
of(1, 2, 3)
.pipe(first())
.subscribe((v) => console.log(`value: ${v}`));
// Logs:
// value: 1
请注意,map 必须在逻辑上动态构建,因为它必须被赋予映射函数。相比之下, first 可能是一个常量,但仍然是动态构建的。作为一般做法,所有运算符都被构造,无论它们是否需要参数。
Piping
可管道操作符是函数,所以它们可以像普通函数一样使用:op()(obs)——但在实践中,往往有许多它们被卷积在一起,很快变得不可读:op4()(op3()(op2() )(op1()(obs))))。出于这个原因,Observables 有一个名为 .pipe() 的方法可以完成同样的事情,同时更容易阅读:
obs.pipe(op1(), op2(), op3(), op4());
就风格而言,即使只有一个运算符,也不会使用 op()(obs) ; obs.pipe(op()) 是普遍首选的。
Creation Operators
What are creation operators?
与可管道操作符不同,创建操作符是可用于创建具有一些常见预定义行为的 Observable 或通过加入其他 Observable 的函数。
创建运算符的典型示例是区间函数。它接受一个数字(不是 Observable)作为输入参数,并产生一个 Observable 作为输出:
import { interval } from 'rxjs';
const observable = interval(1000 /* number of milliseconds */);
Higher-order Observables
Observables 最常发出普通值,如字符串和数字,但令人惊讶的是,它经常需要处理 Observables 的 Observables,即所谓的高阶 Observables。例如,假设您有一个 Observable 发出字符串,这些字符串是您想要查看的文件的 URL。代码可能如下所示:
const fileObservable = urlObservable.pipe(map((url) => http.get(url)));
http.get() 为每个单独的 URL 返回一个 Observable(可能是字符串或字符串数组)。现在你有了一个 Observable 的 Observable,一个高阶的 Observable。
但是如何使用高阶 Observable 呢?通常,通过展平:通过(以某种方式)将高阶 Observable 转换为普通 Observable。例如:
const fileObservable = urlObservable.pipe(
map((url) => http.get(url)),
concatAll()
);
concatAll() 运算符订阅从“外部” Observable 出来的每个“内部” Observable,并复制所有发出的值,直到该 Observable 完成,然后继续下一个。所有的值都以这种方式连接。其他有用的展平运算符(称为连接运算符)是
-
mergeAll() — 在每个内部 Observable 到达时订阅它,然后在它到达时发出每个值
-
switchAll() — 当第一个内部 Observable 到达时订阅它,并在它到达时发出每个值,但是当下一个内部 Observable 到达时,取消订阅前一个,并订阅新的。
-
Exhaust() — 当第一个内部 Observable 到达时订阅它,并在它到达时发出每个值,丢弃所有新到达的内部 Observable,直到第一个完成,然后等待下一个内部 Observable。
就像许多数组库将 map() 和 flat()(或 flatten())组合成一个 flatMap() 一样,所有 RxJS 展平操作符 concatMap()、mergeMap()、switchMap() 和exhaustMap 都有映射等价物()。
Marble diagrams
为了解释运算符的工作原理,文字描述通常是不够的。许多算子都与时间有关,他们可能以不同的方式例如延迟、采样、节流或去抖动值排放。图表通常是更好的工具。大理石图是操作符如何工作的可视化表示,包括输入 Observable(s)、操作符及其参数以及输出 Observable。
有用于不同目的的运算符,它们可以分类为:创建、转换、过滤、加入、多播、错误处理、实用程序等。在下面的列表中,您将找到按类别组织的所有运算符。
Creating custom operators
使用 pipe() 函数创建新的运算符
如果您的代码中有常用的运算符序列,请使用 pipe() 函数将该序列提取到新的运算符中。即使序列不那么常见,将其分解为单个运算符也可以提高可读性。
例如,您可以创建一个丢弃奇数值并将偶数值加倍的函数,如下所示:
import { pipe } from 'rxjs';
import { filter, map } from 'rxjs/operators';
function discardOddDoubleEven() {
return pipe(
filter((v) => !(v % 2)),
map((v) => v + v)
);
}
pipe() 函数类似于 Observable 上的 .pipe() 方法,但与此不同。)
Creating new operators from scratch
它更复杂,但是如果您必须编写一个不能由现有运算符组合而成的运算符(很少发生),您可以使用 Observable 构造函数从头开始编写一个运算符,如下所示:
import { Observable, of } from 'rxjs';
function delay<T>(delayInMillis: number) {
return (observable: Observable<T>) =>
new Observable<T>((subscriber) => {
// this function will be called each time this
// Observable is subscribed to.
const allTimerIDs = new Set();
let hasCompleted = false;
const subscription = observable.subscribe({
next(value) {
// Start a timer to delay the next value
// from being pushed.
const timerID = setTimeout(() => {
subscriber.next(value);
// after we push the value, we need to clean up the timer timerID
allTimerIDs.delete(timerID);
// If the source has completed, and there are no more timers running,
// we can complete the resulting observable.
if (hasCompleted && allTimerIDs.size === 0) {
subscriber.complete();
}
}, delayInMillis);
allTimerIDs.add(timerID);
},
error(err) {
// We need to make sure we're propagating our errors through.
subscriber.error(err);
},
complete() {
hasCompleted = true;
// If we still have timers running, we don't want to yet.
if (allTimerIDs.size === 0) {
subscriber.complete();
}
},
});
// Return the teardown logic. This will be invoked when
// the result errors, completes, or is unsubscribed.
return () => {
subscription.unsubscribe();
// Clean up our timers.
for (const timerID of allTimerIDs) {
clearTimeout(timerID);
}
};
});
}
// Try it out!
of(1, 2, 3).pipe(delay(1000)).subscribe(console.log);
请注意,您必须
1.在订阅输入 Observable 时实现所有三个 Observer 函数 next()、error() 和 complete()。
2.实现一个“拆卸”功能,在 Observable 完成时进行清理(在这种情况下,通过取消订阅和清除任何挂起的超时)。
3.从传递给 Observable 构造函数的函数中返回该拆卸函数。
当然,这只是一个例子; delay() 运算符已经存在。