前言

介绍完 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. 如何做好一场NPS调研?

    我们在工作中经常遇到的一个词,那就是"产品NPS调研".当部分项目缺少专业的用研人员时,设计师.产品经理则经常会接受上级的要求,投身于NPS调研工作. 笔者也曾在2022年的某天突 ...

  2. 引入样式在Element UI (Vue 2)和Element Plus (Vue 3)中的不同

    引入样式 Element UI (Vue 2):   import 'element-ui/lib/theme-chalk/index.css'; Element Plus (Vue 3):   im ...

  3. elementplus django drf 如何做到确认单据禁止删除

    elementplus django drf 如何做到确认单据禁止删除     要在Django和Django Rest Framework(DRF)中实现禁止删除确认单据的功能,你可以通过以下步骤来 ...

  4. vue项目坑记录:vue项目运行卡在百分之几几几

    今天晚上打着游戏,同事突然叫我拉项目下来运行,我打完就去拉代码了,结果vue项目运行卡在66%不动了,我也是百度一下分享别人怎么解决的文章给他,继续我的游戏! 结果呢? 游戏结束后,我拉代码,还是这个 ...

  5. 30FPS和120FPS在游戏中的区别

    30FPS和120FPS的区别: 从动画上,时间尺度更小,渲染的时候物体单帧移动距离更小从物理引擎计算上,每一次的迭代更细致,计算更精确从渲染上:从触摸事件上,响应更及时,从触摸到屏幕,到系统捕捉,到 ...

  6. python global将结果存储起来给另外一个文件对象使用

    python global将结果存储起来给另外一个文件对象使用 使用场景: 在aaa.py文件里面操作数据生成结果C 然后再在bbb.py文件里面使用C 下面是aaa.py代码: #!/usr/bin ...

  7. 9、IDEA集成Github

    9.1.登录Github账号 9.1.1.打开IDEA的Settings界面 如上图所示,打开IDEA的 Settings(设置)界面. 9.1.2.使用账号密码登录(方式一) 如上图所示,在&quo ...

  8. 【SQL】 牛客网SQL训练Part3 较难难度

    获取当前薪水第二多的员工的emp_no以及其对应的薪水salary 请你查找薪水排名第二多的员工编号emp_no.薪水salary.last_name以及first_name,不能使用order by ...

  9. 【H5】02 <head>头标签介绍

    摘自: https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Introduction_to_HTML/The_head_metadata_in_HT ...

  10. 【GPU】如何两周内零经验手搓一个GPU | 美国工程师极限挑战 | 重写三次 | CUDA | SIMD | ISA指令集 | Verilog | OpenLane

    地址: https://www.youtube.com/watch?v=FTh-c2ek6PU