Fork me on GitHub

RxJs系列(四):常用操作符

话说人一旦懒起来还真是容易刹不住车啊,又是差不多有一个月没有写博客了。

今天来接着说一说RxJs ,话说上一篇博客介绍了一些常见的Observable创建操作符,那这篇博客就来说一说用于对Observable进行操作的一些操作符。

这类操作符数量不少,而且作用各式各样,有相当一部分操作符很难通过单纯的文字介绍透彻的说明它们的作用。不过还好,根据28原则来说,相当一部分技术概念我们在实际开发中不是那么常遇见。这篇博客,就介绍一些比较常用,而且理解了这些操作符可以帮助我们快速的上手使用RxJs,最终可以使用操作符高效的实现异步流程控制。

在关于RxJs的文章里我曾经看到过一句话,当你遇到一个RxJS无法模拟的异步流程时,你首先要反思一下自己是不是真的掌握了RxJs,这句话有些高调,但我觉得不是说谎。

Ok,进入正文。

在前几篇博客中,我们提到了Rxjs的通过与函数式编程类似的操作符可以较好的抽象我们的常规逻辑操作,使我们方便的处理异步逻辑。

对于RxJs的学习来说,操作符是比较重要的一部分,也是比较具有难度的一部分,主要有以下几点原因:

  • 操作符数量比较多
  • 部分操作符比较抽象,难以直观的去理解
  • 大部分操作符应用于复杂的异步逻辑操作,也很难通过简单的demo体现作用

也因为这些原因,关于RxJs的教程中,很少有能清楚明白使新手能快速掌握的操作符介绍。我这篇博客估计也很难完整清楚的讲清楚操作符的细节。

但只要理解了操作符的大概模式和作用,用的到的时候再去查阅,如此反复几次,也就能够形成一个RxJs的思维图谱了。

操作符,通常的模式即接受一个Observable,然后通过组合,过滤,合并,缓存等操作,来生成一个新的Observable流。

首先,来介绍几个比较简单和容易理解的操作符。

对于函数式编程来说,非常常见的三个函数map, filterreduce,在RxJs也有三个对应的操作符,map,filter,scan

map

map操作符接受一个函数,Observable的每个值都会作为参数传入这个函数,并根据函数的返回值形成一个新的Observable

这个操作符比较容易理解,与大部分编程语言中的map作用类似。

javascript
1
2
3
4
5
6
7
8
9
10
11
const source$ = interval(1000)

source$.pipe(
map (val => val * val)
).subscribe(console.log)

// 0
// 1
// 4
// 9
// ....

如上所示,源Observable每隔一秒发出一个递增1的值,map操作符将这个值平方作为新的Observable的值发出。

filter

不需要多说,这个操作符也和大部分编程语言中的filter类似。

javascript
1
2
3
4
5
6
7
8
9
10
11
const source$ = interval(1000)

interval.pipe(
filter (val => val % 2)
).subscribe(console.log)

// 1
// 3
// 5
// 7
// ....

reduce

reduce操作符类似JsPython中的reduce,接受一个函数和一个seed,对Observable的值进行累计处理, 并在Observable完成时返回计算结果。

javascript
1
2
3
4
5
6
7
8
const source$ = range(100)

source$.pipe(
map(e => 1)
reduce((acc, val) => {
acc + val
}, 0)
).subscribe(console.log)

scan

scanreduce操作符类似,都是用来计算累加值的,不同点在于,reduce会在Observable完成时才返回总计结果,而 scan每计算一次就会返回一次结果。

javascript
1
2
3
4
5
6
7
8
9
const btnDom = document.querySelector('.btn')
const source$ = fromEvent(btnDom, 'click')

source$.pipe(
map(e => 1)
reduce((acc, val) => {
acc + val
}, 0)
).subscribe(console.log)

在上面这个例子中,我们单纯通过Observable流,而不用借助任何外部变量,就记录了按钮的点击次数。这也体现了RxJs的优势: 可以通过异步流来记录状态。

上面是四个比较简单的操作符,在日常使用中,我们比较常使请求也参与到异步流程中,还会用到另一个操作符:switchMap

switchMap

switchMap操作符,用于将Observable的每一个值映射为另一个Observable流,并始终返回这些映射流中最新的一个。

我们使用一个例子来说明:

javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
const btnDom = document.querySelector('#btn')
const source$ = fromEvent(btnDom, 'click')

const function getData() {
return fetch('https://jsonplaceholder.typicode.com/posts/1')
.then(res => res.json())
}

source$.pipe(
switchMap(
e => fromPromise(getData())
)
).subscribe(console.log)

我们每次点击按钮,都会触发一个请求,通过switchMap操作符, 我们只会接受到最新的请求返回的值,旧请求会被丢弃。

switchMap类似,还有两个操作符concatMapmergeMap, 一个可以将所有请求串行起来,一个可以将所有请求并行起来,这三个操作符,在我们通过UI操作触发各式各样的请求时非常有用。

debounceTime

debounceTime操作符用于进行防抖处理,也是我们处理事件或其他异步操作时比较常用的操作符。

例如上面的点击按钮请求的例子中,我们想在点击按钮一秒后未再次点击时才发出请求,就可以加上这个操作符

javascript
1
2
3
4
5
6
source$.pipe(
debounceTime(1000),
switchMap(
e => fromPromise(getData())
)
).subscribe(console.log)

对应防抖操作符,还有一个节流操作符throttleTime,节流操作符主要用于我们点击之后的一段时间,不再触发。

debouncethrottle在我们处理UI事件和请求操作时,都非常有用。

retry

在网络请求中,我们还需要处理异常情况,对于某些异常,我们可能想要进行重试,此时就可以使用retry操作符。

还是上面的例子,当请求出现错误时,我们重试三次,如果还是错误,就抛出错误:

javascript
1
2
3
4
5
6
7
source$.pipe(
debounceTime(1000),
switchMap(
e => fromPromise(getData())
),
retry(3)
).subscribe(console.log)

retry操作符会在Observable发生错误时捕获错误,并重试源Observable指定次数。

关于常用的几个操作符,就先介绍这么几个。

还有相当多各式各样的操作符,在学习RxJs的过程中,或多或少会见到它们,在遇见的时候去翻阅一下文档或源码,查一下资料,还是不难理解的。

为了更直观更清晰的理解各种操作符的作用,同时也能够使大家了解RxJs的实际应用,我创建了一个项目 powerful-rxjs。想体验rxjs的同学,可以对照查看,对于rxjs的初步理解会有很大帮助。我也会在后续不断添加新的demo进去。

Ok,本篇博客就到这里,多谢阅读。

----本文结束感谢阅读----