前言

介绍完 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. CF466E Information Graph 题解

    题目链接 Luogu Codeforces 题意简述 某公司中有 \(n\) 名员工.为方便起见,将这些员工从 1 至 \(n\) 编号.起初,员工之间相互独立.接下来,会有以下 \(m\) 次操作: ...

  2. ASP.NET Core 程序集注入(三)

    前言: 在Autofac的使用中,提供了个种注入的API其中GetAssemblies()用着特别的舒坦. 1.core2.0也可以使用Autofac的包,但框架自身也提供了默认的注入Api,ISer ...

  3. Django--StreamingHttpResponse下载文件

    from django.shortcuts import render, HttpResponse from django.http import StreamingHttpResponse impo ...

  4. [UE] 关于ue5中制作流日志记录

    UE5目前根据现有功能,配合Quixel Bridge可以做到地编和一些简单的动画,实现完整的游戏,但是目前随着版本的迭代,流程的定制需要更新 ControlRig方便在UE中做动画的,模拟动画等,U ...

  5. pytest-req插件:更简单的做接口测试

    pytest-req插件:更简单的做接口测试 背景 我们经常会用到 pytest 和 requests 进行接口自动化测试. pytest 提供了非常方便的插件开发能力,在pytest中使用reque ...

  6. wireshark抓包分析数据

    wireshark抓包分析数据 https://www.cnblogs.com/moonbaby/p/10528401.html https://blog.csdn.net/wangyiyungw/a ...

  7. .NET 8 通用权限框架 前后端分离,开箱即用

    前言​ 推荐一个基于.NET 8 实现的通用权限开发框架Admin.NET,前端使用Vue3/Element-plus开发. 基于.NET 8(Furion)/SqlSugar实现的通用管理平台.整合 ...

  8. FFmpeg在游戏视频录制中的应用:画质与文件大小的综合比较

    我们游戏内的视频录制目前只支持avi固定码率,在玩家见面会上有玩家反馈希望改善录制画质,我最近在研究了有关视频画质的一些内容并做了一些统计. 录制视频大小对比 首先在游戏引擎中增加了对录制mp4格式的 ...

  9. mpi4py.MPI.COMM_WORLD.Get_size失败——mpiexec and python mpi4py gives rank 0 and size 1 —— MPI.COMM_WORLD.Get_size() is always resulting '1'

    参考: https://stackoverflow.com/questions/29264640/mpiexec-and-python-mpi4py-gives-rank-0-and-size-1 = ...

  10. 【转载】 Makefile的静态模式%.o : %.c

    版权声明:本文为CSDN博主「猪哥-嵌入式」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明.原文链接:https://blog.csdn.net/u012351051 ...