运算符是什么
运算符是rxjs的核心能力.它们是一系列函数,可以对流进行转化、时序控制等.
有的运算符接受流作为参数 得到一个新流
另一部分运算符则通过这样的方式进行串联
stream$.pipe(op1(),op2())
这样的操作可以得到一个受运算符控制的新流
常见的运算符
提醒:rxjs的流不只是数据流 可以理解为事件、promise、数组等一系列概念的统一封装 "某个流发出数据"实际可能指的是"某次事件触发" "某个Promise完成"等
数据转化
map
stream$.pipe(map((v) => `${v}`))
将每个上游数据映射为新数据
scan
stream$
.pipe(
scan((result, currentValue, index) => {
return result + currentValue
}, 0),
)
类似数组的reduce 进行值的累加
scan会将每次的result向外发出
如果不提供初始值 则直接从第二个数据开始执行函数 result是第一个数据 index是1
pairwise
from([1, 2, 3])
.pipe(pairwise())
将上一个值和当前值组成数组
订阅这个流会得到[1,2] [2,3].如果数组只有一个元素 则不会得到结果.
bufferTime/bufferCount/buffer
管道运算符 可以缓存上游数据并打包为数组发出
bufferTime是缓存一定毫秒 bufferCount是缓存一定数量; buffer则接受一个流close$, 每当close$输出一个值 都清空当前缓存
多个流的数据组合
combineLatest
combineLatest([a$, b$])
在数组内任一流发出数据后 如果所有流都至少发出过一次数据 将这些流最新数据打包为数组发出
zip
zip([a$, b$])
将所有流的第1/2/.../n个数据组合为数组输出.同一数组内 输出的数据索引相同
forkJoin所有流的最后一个值(或者说 在所有流结束后做某些事)withLatestFrom
stream$.pipe(withLatestFrom(a$, b$))
主流stream$输出数据后 如果副流a$ b$都至少输出了一次数据 将主流数据与副流的最新数据打包为数组输出.
如果副流里有一个没输出数据 则此次数据会被忽略 如果需要不错过每次数据 使用combineLatest即可
需要注意的是 withLatestFrom的结束仅与主流有关 而combineLatest则需要接受的所有流都结束
race接受一个流数组.所有流中最先发出数据的会成为唯一的数据源 其余被忽略
流类型的数据
rxjs的流对象也可以作为另一个流传输的数据.
从上游接受流类型的数据,订阅该流并将其中数据依次输出的过程,就叫流的扁平化.
这其实和数组扁平化很像,因为数组的扁平化是将Array<Array<number>>变为Array<number>,而流的扁平化是将Observable<Observable<number>>变为Observable<number>.
注意:扁平化运算符不仅可以接受流 也能接受数组、promise等流式数据源 会被自动转化为流再扁平化
concat
concat(a$, b$)
这个运算符会先将a$扁平化(依次输出其中的数据)直至其结束 然后按顺序逐个扁平化后续的流
适用于串行任务 例如多个必须按照顺序执行的网络请求
merge
merge(a$, b$)
这个运算符会同时扁平化接受的所有流.即同时订阅参数里的所有流,无论哪个流发出数据都立刻向外输出.
适用于并行任务 例如多个同时上传的文件
concatAll/concatMap
concatAll是管道运算符 可以将上游流类型数据以类似concat的方式扁平化 适用场景也与之类似
declare const doTask: (t: number) => Promise<void>
of(1, 2).pipe(
map((t) => defer(() => doTask(t))),
concatAll()
)
// 这里用defer包了一层 以确保当前任务执行时 上一个任务已经执行完毕
// defer也能接受流式数据源
concatMap是map和concatAll的简写 上述示例可以简写为
of(1, 2).pipe(
concatMap((t) => defer(() => doTask(t)))
)
mergeAll/mergeMap/mergeScan
mergeAll是merge的管道运算符版本 会将上游流数据同时扁平化.
mergeMap是map和mergeAll的简写,mergeScan是scan和mergeAll的简写.
它们都可以接受一个数字 用于控制最大活跃自流数量(类似于最大并发数)
-
exhaustAll/exhaustMap
exhaustAll会订阅上游的流类型数据并将其扁平化.在当前订阅的流未完成时 接收到新的上游数据会被忽略.
适用于旧任务进行时丢弃新任务的场景 例如多次点提交 在第一次提交结束前都不生效
exhaustMap是map和exhaustAll的简写
exhaust系列没有静态运算符 -
switchAll/switchMap/switchScan
switchAll会订阅上游的流类型数据.在接受到新的上游数据后,会结束当前流的订阅,改为订阅新的流.
适用于新任务出现时立刻丢弃旧任务的场景 例如按照搜索框内容进行联想
switchMap/switchScan是简写
switch系列没有静态运算符 -
windowTime/windowCount/window
window系列与buffer类似 也会缓存数据.
与buffer不同的是 window会将这些数据打包为一个流 -
groupBy
stream$.pipe(groupBy((v) => v)).subscribe((v) => {
v.subscribe(console.log)
})
可以对上游数据进行分组 分组结果也是一个流.
每当出现一个新的组别,就输出一个新的自流,后续同组的上游数据就不再向外输出,而是输出到对应子流中.子流有一个key属性 可以判断它所属的类别
数据筛选
- filter
from([1, 2])
.pipe(filter((v) => v !== 1))
类似数组的filter 按值进行筛选
-
take系列taketake(n)取上游n个值后取消订阅takeLasttakeLast(n)上游最后n个数据takeWhiletakeWhile(fn)取值直至fn返回false 然后取消订阅takeUntiltakeUntil(close$)取值直到close$输出数据然后取消订阅
-
skip系列 具有与take系列相似的四个函数skipXXX可以跳过符合条件的若干值 -
first/last
stream$
.pipe(first())
stream$
.pipe(first(v=>v.id>0))
first的第一个参数不传或者是null/undefined时 它会取流的第一个数据;如果是一个返回布尔值的函数 此时它会取第一个满足条件的数据
第二个参数是defaultValue 如果流结束时没有返回数据 就会返回这个值 若不传就会抛出异常
last与first类似
elementAt取流的第n个数据
stream$
.pipe(elementAt(1)) // 取第一个数据
它的第二个参数也是默认值 用于上游数据不足的情况 无默认值则抛异常
-
distinct/distinctUntilChanged用于去重.distinct对于整个流的数据去重,distinctUntilChanged则过滤与上个数据相同的数据 -
delay/delayWhen为每个上游数据延迟一定时间/延迟到指定流发出数据.可以出现后发先至的情况 -
debounceTime/debounce/throttleTime/throttle针对上游数据的防抖节流.可以选择用时间控制或给出一个流控制 -
auditTime/audit另一种形式的节流.audit会在窗口结束后发出其中的最后一个数据 而throttle则是发出一个数据后再创建窗口 -
sampleTime/sample定期/在指定流发出数据后 从主流中发出最新的数据(抽样调查)
错误处理
上游一旦抛出错误 下游便会触发onError 并且订阅会立刻结束.因此map时需要注意可能的异常并将其捕获 并在其后用filter过滤空值.
非map引起的异常可以考虑使用以下的运算符
catchError管道运算符.捕获一个异常 并返回一个备用流onErrorResumeNext
onErrorResumeNext(a$, b$)
一个流报错后 自动切换下一个流
retry错误重试.
stream$.pipe(retry(3)) // 直接指定次数
stream$.pipe({
delay: () => a$ // a$发出数据后重试
})
stream$.pipe({
delay: () => EMPTY // 没有数据直接完成的话 直接将整个管道置为已完成 不抛出异常
})
stream$.pipe({
delay: () => EMPTY // 没有数据直接完成的话 直接将整个管道置为已完成 不抛出异常
})
可以直接指定次数 也可以在 在该流发出数据时进行重试.需要注意的是 重试的是整个管道 就是.pipe之前的那个 会重新订阅
统计类
这类管道运算符会在上游数据结束后发出一个总结性质的数据
toArray所有上游数据的数组count数据总数reduce类似数组reducemax/min字面意思every类似数组everyfind/findIndex字面意思isEmpty字面意思
其他
tap执行副作用 不改变流数据finalize流结束或出错后执行函数 流版本的.finallytimeout超时报错timeoutWith超时替换备用流startWith/endWith
startWith在上游传输数据之前 先发出指定的一个或多个数据.
endWith则是在上游结束之后 发出指定的数据.timeInterval为数据加上与上一次数据的间隔时间timestamp为数据添加时间戳repeat
stream$.pipe(repeat(3)) // 传入时间
stream$.pipe(repeat({
count: 1 // 默认是无限
delay: ()=>a$ // 传入一个流 在其发出数据时重试
}))
重复次订阅整个管道.可以传入次数或者流控制.传入次数表示重复总次数 而不是额外的重复次数.