前言

介绍完 RxJS 两大概念 ObservableSubject 之后, 篇幅最大的就是各种 Operators 了.

这篇先介绍比较简单的 Filter Operators. 请先看完上面 2 篇哦. 因为例子会用到一些之前介绍过的方法.

参考

Docs – Filtering Operators

超级多, 这里我只介绍我自己有用过, 或者以后有可能会用到的. 顺序会依照我最常用和相关性排.

filter

const source = from([1, 2, 3, 4]);
const odd$ = source.pipe(filter(v => v % 2 !== 0));
const even$ = source.pipe(filter(v => v % 2 === 0));
odd$.subscribe(v => console.log(v)); // 1..3 (only value 1 and 3 will call)
even$.subscribe(v => console.log(v)); // 2..4

之前提过, stream 和 Array 很像. 所以它也有 filter 的概念.

每当发布一个 new value, 先经过 filter, 如果 ok, 最终 subscriber 才能接收到.

first

顾名思义, 就是第一个发布的 value 才接收

const source = from([1, 2, 3, 4]);
source.pipe(first(v => v % 2 !== 0)).subscribe({
next: v => console.log(v),
complete: () => console.log('complete'),
}); // 1 'complete'

此外, first 可以传入 filter 参数. 上面这个表示, 第一个 odd value 才接收.

同时, 当接受完第一个发布后, complete 也会随即触发. 因为 first 表示只要第一个, 拿到自然就结束退订了.

last

有 first 自然就有 last. 不过 last 比较可爱的地方是, 怎么知道是最后一个呢?

所以它需要一个 complete 才能确保哪一个是最后一个.

const obs = new Observable<number>(subscriber => {
subscriber.next(1);
subscriber.next(2);
subscriber.next(3);
subscriber.next(4);
subscriber.complete(); // 重要
});
obs.pipe(last(v => v % 2 !== 0)).subscribe(v => console.log(v)); // 3

如果少了 complete, subscriber 将永远不会接收到 last value.

elementAt

指定拿第几个发布值.

const obs = from([1, 2, 3, 4]);
obs.pipe(elementAt(2)).subscribe(v => console.log(v)); // 3

如果 Observable 结束都没有达到指定的 index, 它会报错, 可以通过设置 default value 解决这个报错.

const obs = from([1, 2, 3, 4]);
obs.pipe(elementAt(100, 'default value')).subscribe(v => console.log(v)); // 'default value'

skip

顾名思义, 就是跳过几个

const source = from([1, 2, 3, 4]);
source.pipe(skip(2)).subscribe(v => console.log(v)); // 3..4

skip(2) 表示前面 2 次发布不接收.

skipLast

const source = from([1, 2, 3, 4]);
source.pipe(skipLast(2)).subscribe(v => console.log(v)); // 1..2

和 last 一样, Observable 一定要有 complete 哦, 不然不知道哪个算 last.

skipUntil

一直 skip 直到参数 Observable 发布

const obs = interval(1000); // 0..1..2..3..4..5
obs.pipe(skipUntil(timer(4000))).subscribe(v => console.log(v)); // 3..4..5

obs 每一秒发布一下. skipUntil 的参数 Observable 4 秒后发布. 于是前面 4 秒的值都被 skip 掉了.

为什么 3 没有被 filter, 那时因为 interval 是慢一拍的. 之前文章有解释过了.

如果觉得乱可以改成

const obs = timer(0, 1000); // 0..1..2..3..4..5
obs.pipe(skipUntil(timer(4000))).subscribe(v => console.log(v)); // 4..5

skipWhile

const source = from([1, 2, 3, 4]);
source.pipe(skipWhile(v => v <= 2)).subscribe(v => console.log(v)); // 3..4

skip 和 skipUntil 都没有办法依据 value 来判断是否持续 skip. 而 skipWhilte 补上了这个缺失.

上面的例子表示, 只要 value <=2 就一直 skip 下去, 直到 value > 2 出现, 才开始接收.

take

take 和 skip 是相反的概念

skip(2) 表示头 2 个 "不要", 剩余的都 "要"

take(2) 表示头 2 个 "要", 剩余的 "不要"

const source = from([1, 2, 3, 4]);
source.pipe(take(2)).subscribe({
next: v => console.log(v), // 1..2
complete: () => console.log('complete'), // complete when dispatch 2
});

另外, 由于剩余的都不要了, 所以它会提前结束 stream displose Observable.

takeLast

const source = from([1, 2, 3, 4]);
source.pipe(takeLast(2)).subscribe(v => console.log(v)); // 3..4

只拿最后 2 个. 但凡有 "last" 的 operators, Observable 记得要 complete 哦, complete 了以后 subscriber 才会接收到 value.

takeUntil

const obs = timer(0, 1000); // 0..1..2..3..4..5
obs.pipe(takeUntil(timer(3000))).subscribe(v => console.log(v)); // 0..1..2

一直拿到 3 秒, 往后的都不要了

takeUntil vs unsubscribe

takeUntil 的逻辑是,当 takeUntil 的 parameter 流发布后,takeUntil 会 unsubscribe 自己上游的流并且发布一个 complete 给下游。

unsubscribe 则是退订一个流。

takeUntil 可以在中间,退订上游,complete 下游,unsubscribe 则一定是最下游开始。

takeUntil 有 complete,unsubscribe 没有。

如果把 takeUntil 放到最下游,其效果和 unsubscribe 几乎一样 (除了 takeUntil 会发布 complete),

如果把 takeUntil 放到中游或上游,它是退订上游,不再接收上游的发布,可是之前已经接收但下游还没有 process 完的依然是会继续执行的,这一点要特别注意。

takeWhile

const source = from([1, 2, 3, 4]);
source.pipe(takeWhile(v => v <= 2)).subscribe(v => console.log(v)); // 1..2

依据 value 判断要拿到什么时候停。

它有一个 inclusive 参数

const source = from([1, 2, 3, 4]);
source.pipe(takeWhile(v => v <= 2, true)).subscribe(v => console.log(v)); // 1..2..3

默认是 false,设置 true 的意思是第一个匹配失败的也会发布。

distinct

const source = from([1, 2, 3, 3, 2, 1, 4]);
source.pipe(distinct()).subscribe(v => console.log(v)); // 1..2..3..4

但凡出现过的 value 就不再第二次接收.

key selector

value 的 compare 方式是 ===

这种 compare 最怕就是遇到对象了. 因为有时候对象指针虽然不同, 但其实是同一个 value 来的.

const source = from([
{ id: 1, name: 'Derrick' },
{ id: 2, name: 'Xinyao' },
{ id: 1, name: 'Derrick' },
]);
source.pipe(distinct(v => v.id)).subscribe(v => console.log(v)); // 1..2

这时就可以提供一个 key selector 函数. 这样对比的方式从 v === v 变成了 v.id === v.id

distinctUntilChanged

distinct 是指, 但凡过往只要出现过, 那么就不接收.

distinctUntilChanged 则是, 如果上一个 value 和当前这一个一样的话, 那就不接收. 重点在它只 care 上一个而且, distinct 是看过往所有的 value.

const source = from([1, 2, 3, 3, 3, 2, 1, 4, 3]);
source.pipe(distinct()).subscribe(v => console.log(v)); // 1..2..3..4
source.pipe(distinctUntilChanged()).subscribe(v => console.log(v)); // 1..2..3..2..1..4..3

distinctUntilChanged 只 filter 了中间区域的两个 3. 因为中间连续了三个 3.

key selector and comparator

distinctUntilChanged 比 distinct 更 cutomize, 它除了可以提供 key selector, 甚至连 compare 的方法想从 === 换成 == 都可以

distinctUntilKeyChanged

source.pipe(distinctUntilKeyChanged('id')).subscribe(v => console.log(v));

它底层只是调用了 distinctUntilChanged 而已. 没有必要学这个, 用 distinctUntilChanged 就可以了.

debounceTime

debounceTime 常用于限制过于频密的发布, 比如 user typing ajax search

当用户连续打字的时候, 我们不会发送 ajax search, 直到用户停止输入后才发 ajax.

const subject = new Subject<string>();
subject.pipe(debounceTime(2000)).subscribe(v => console.log(v));
subject.next('a');
await delayAsync(1000);
subject.next('b'); // 还不到 2 秒又发布了, a 被取消接收
await delayAsync(1000);
subject.next('c'); // 还不到 2 秒又发布了, b 被取消接收
// 2 秒后不再有发布, c 被接收

debounceTime(2000) 表示, Subject 发布后, 会先观察 2 秒, 如果没有下一个发布, 那么这个值将被接收, 如果 2 秒内又有新的发布, 那就就丢弃上一个值, 继续观察下一个 2 秒.

所以, 如果每一次发布间隔都小于 2 秒, 那么观察会一直进行下去, 不会有任何接收.

debounce

参考: RxJS 学习系列 10. 过滤操作符 debounce,debounceTime,throttle,throttleTime

debounce 比较少用到, 它和 debounceTime 类似, 都是用于限制频密发布.

但它比较厉害的地方是可以控制 duration. debounceTime 只能 set 死一个秒. debounce 可以动态控制

const subject = new Subject<string>();
const durationSelector = (value: string) => timer(2000);
subject.pipe(debounce(durationSelector)).subscribe(v => console.log(v));

每当 Subject 发布, durationSelector 就会被调用, 它会拿到 Subject 发布的值, 然后返回一个 duration observable

这个 duration observable 就是一个观察期. 在 duration observable 发布以前, 如果 subject 又发布, 那么上一次 subject 发布的值就被丢弃. 然后 durationSelector 再次被调用. 等待下一个观察期.

throttleTime

throttleTime 和 debounceTime 类似, 都是限制频密发布的.

区别在于, 当 Subject 发布后, 会立马接收, 然后进入关闭期. 在关闭期间, 如果 Subject 又发布, 那么就拒绝接收 (filter 掉)

const subject = new Subject<string>();
subject.pipe(throttleTime(1000)).subscribe(v => console.log(v));
subject.next('v'); // 接收
subject.next('v'); // 拒绝 (因为在 1 秒内又发布了)
await delayAsync(1000);
subject.next('v2'); // 接收 (因为已经过了 1 秒的关闭期)

throttle

throttle & throttleTime 的关系就和 debounce & debounceTime 关系一样.

就是多了一个 durationSelector 可以动态声明关闭期.

auditTime

auditTime和 debounceTime 类似, 都是限制频密发布的.

区别在于, auditTime 不会 reset 观察期.

const subject = new Subject<string>();
subject.pipe(auditTime(3000)).subscribe(v => console.log(v)); // v3..v5
subject.next('v1'); // 拒绝
await delayAsync(1000);
subject.next('v2'); // 拒绝
await delayAsync(1000);
subject.next('v3'); // 接收
await delayAsync(1000);
subject.next('v4'); // 拒绝
await delayAsync(1000);
subject.next('v5'); // 接收

观察期是 3 秒, 虽然 Subject 一直在发布, 但是不会 reset 观察期, 所以 3 秒钟一到 v3 就接收到了.

换成是 debounceTime 的话, v1, v2, v3, v4 都会被拒绝, 因为每一次观察期会不断被 reset (一直重新观察 3 秒)

audit

audit & auditTime 的关系就和 debounce & debounceTime 关系一样.

就是多了一个 durationSelector 可以动态声明观察期.

debounceTime, throttleTime, auditTime 总结

debounceTime (delay and keep postpone) : 发布...进入观察期(不马上接收)...再发布...丢弃上一个, 重新观察期(不马上接收)...观察期结束(接收)

throttleTime (immediately + skip) : 发布...关闭期(马上接收)...再发布...skip...关闭期结束...再发布...关闭期(马上接收)...循环

auditTime (delay only, no keep postpone) : 发布..观察期(不马上接收)...再发布...丢弃上一个(但不开启新的观察期了)...观察期结束(接收)

sampleTime

sampleTime 和上面的 debounceTime 它们没有关系. 用途不一样.

sampleTime 是按一个间隔时间去取值 (取样) 的概念

(async () => {
const subject = new Subject();
subject
.pipe(sampleTime(3000))
.subscribe(v => console.log('sample time ' + parseInt(performance.now().toString()), v));
await delayAsync(2000);
subject.next(1);
await delayAsync(2000);
subject.next(2);
subject.next(2.1);
subject.next(2.2);
await delayAsync(5000);
subject.next(3);
})();

sampleTime(3000) 表示每隔 3 秒, 去检查 subject 是否有发布新值, 如果有那么就接收, 如果没有就继续等下一个 3 秒在检查.

效果

3 秒的时候拿到 1

6 秒的时候拿到 2.2 (虽然 Subject 发布了 3 次, 但只接收最新的值)

9 秒的时候 subject 没有发布新值, 所以也就没有接收

12 秒的时候拿到 3

sample

sample 和 sampleTime 功能是一样的, 区别在于检测的 timing

sampleTime 只能设定间隔时间, sample 则可以传入任何 Observable, 所以可以传入 click$, 这样每次点击的时候就去取样.

没有介绍到的 Filter Operators

ignoreElements

single

一句话总结

filter : 条件过滤

first : 拿第一个

last : 拿最后一个, 记得 complete

elementAt : 拿第 n 个

skip : 跳过头 n 的

skipLast : 跳过最后 n 个, 记得 complete

skipUntil : 一直跳过, 直到 Observable 发布

skipWhile : 跳过依据 value

take : 只拿头几个, 拿够了自动 complete

takeLast : 只拿最后几个, 记得 complete

takeUntil : 拿头几个, 直到 Observable 发布

takeWhile : 拿投几个, 依据 value 判断

distinct : 但凡出现过就不接收

distinctUntilChanged : 和上一个一样就不接收

distinctUntilKeyChanged : 只是 distinctUntilChanged 语法糖

debounceTime : delay and keep postpone

debounce : dynamic 控制 time

throttleTime immediately + skip

throttle : dynamic 控制 time

auditTime : delay only, no keep postpone

audit : dynamic 控制 time

sampleTime : 定时取样, Observable 没有发布就 skip

sample : 控制 time

RxJS 系列 – Filtering Operators的更多相关文章

  1. [RxJS] Filtering operators: take, first, skip

    There are more operators in the filtering category besides filter(). This lesson will teach how take ...

  2. [RxJS] Filtering operators: distinct and distinctUntilChanged

    Operator distinct() and its variants are an important type of Filtering operator. This lessons shows ...

  3. [RxJS] Filtering operators: takeLast, last

    Operators take(), skip(), and first() all refer to values emitted in the beginning of an Observable ...

  4. [RxJS] Filtering operators: throttle and throttleTime

    Debounce is known to be a rate-limiting operator, but it's not the only one. This lessons introduces ...

  5. [RxJS] Filtering operators: skipWhile and skipUntil

    After takeUntil() and takeWhile() function, let's have a look on skipWhile() and skilUntil() functio ...

  6. [RxJS] Filtering operators: takeUntil, takeWhile

    take(), takeLast(), first(), last(), those opreators all take number or no param. takeUtil and takeW ...

  7. LINQ Operators之过滤(Filtering)

    转:http://www.cnblogs.com/lifepoem/archive/2011/11/16/2250676.html 在本系列博客前面的篇章中,已经对LINQ的作用.C# 3.0为LIN ...

  8. LINQ之路11:LINQ Operators之过滤(Filtering)

    在本系列博客前面的篇章中,已经对LINQ的作用.C# 3.0为LINQ提供的新特性,还有几种典型的LINQ技术:LINQ to Objects.LINQ to SQL.Entity Framework ...

  9. RxJS——调度器(Scheduler)

    调度器 什么是调度器?调度器是当开始订阅时,控制通知推送的.它由三个部分组成. 调度是数据结构.它知道怎样在优先级或其他标准去存储和排队运行的任务 调度器是一个执行上下文.它表示任务在何时何地执行(例 ...

  10. rxjs笔记(未完成)

    首先是 Observable 和promise的区别, 1返回值个数,Observable 可以返回0到无数个值. 2.Promise主动推送,控制着"值"何时被 "推送 ...

随机推荐

  1. 推荐2款.NET开源、轻便、实用的Windows桌面启动器

    Flow Launcher Flow Launcher是一款.NET开源(MIT License).免费.功能强大.方便实用的 Windows 文件搜索和应用程序启动器,能够帮助你快速查找文件.启动应 ...

  2. mysql 临时表的好处

    客户端新建了一个会话,这个会话只是服务器与客户端1对1的关系,客户端可能在服务端建立一个临时表,满足客户端处理某些事务的需求,当客户端退出会话后,这个临时表自动drop,没有任何数据信息占用数据库空间 ...

  3. card 卡片 html

    {% extends 'base.html' %} {% block content %} <div class="container"> <h1>客户信息 ...

  4. Java实现快速快速排序算法

    算法简介 快速排序(Quick Sort) 是由冒泡排序改进而得的.在冒泡排序过程中,只对相邻的两个记录进行比较,因此每次交换两个相邻记录时只能消除一个逆序.如果能通过两个(不相邻)记录的一次交换直接 ...

  5. [oeasy]python0073_进制转化_eval_evaluate_衡量_oct_octal_八进制

    进制转化 回忆上次内容 上次了解的是 整型数字类变量 integer 前缀为i   ​   添加图片注释,不超过 140 字(可选)   整型变量 和 字符串变量 不同 整型变量 是 直接存储二进制形 ...

  6. 构建基于Java Spring Boot和Uniapp的心理小程序:从零到一的完整指南

    构建基于Java Spring Boot和Uniapp的心理小程序:从零到一的完整指南 前言 大家好,今天我们来聊聊如何使用Java Spring Boot和Uniapp构建一个心理小程序.这个项目不 ...

  7. PKUWC2024游记

    PKUWC2024 游记 day -???? 得知今年冬令营在育才,非常高兴不用出远门了. day 1 当天上午 7:00 起来,然后做车去报道,非常堵车.感觉育才环境挺好的,~不像某人在读学校一样. ...

  8. 对比python学julia(第一章)--(第五节)八十天环游地球

    5.1.  问题描述 <八十天环游地球>是法国作家儒勒·凡尔纳创作的一部长篇小说,讲述了这样一个神奇的故事. 在1872年的伦敦,英国绅士福格跟俱乐部的朋友以巨资打赌他能在80天实现环游地 ...

  9. 【Mybatis-Plus】04 AR (Active Record)

    AR模式,全称激活记录 具体操作更接近Hibernate一样的OOP操作方式影响数据库记录 比Hibernate操作更灵活更方便 上手: 首先User实体类需要继承Model类并泛型注入User类型 ...

  10. Google的蛋白质结构预测项目代码(Demo)

    相关: Artificial Intelligence | 60 Minutes Full Episodes