(二)温故知新系列之RXJS——RXJS操作符基础(创建类)

175 阅读6分钟

一、什么是操作符?

上一篇说了 Rxjs 的基本概念,后面几篇都是说操作符相关的。操作符是 Observable 类型上的方法,比如 .map(…)、.filter(…)、.merge(…),等等。当操作符被调用时,它们不会改变已经存在的 Observable 实例。相反,它们返回一个新的 Observable ,它的 subscription 逻辑基于第一个 Observable 。

就像⼀个管道,数据从管道的⼀段流⼊,途径管道各个环 节,当数据到达Observer的时候,已经被管道操作过,有的数据已经被中 途过滤抛弃掉了,有的数据已经被改变了原来的形态,⽽且最后的数据可 能来⾃多个数据源,最后Observer只需要处理能够⾛到终点的数据。

在RxJS中,组成数据管道的元素就是操作符。

image.png

对于每⼀个操作符,链接的就是上游(upstream)和下游。

image.png

对⼀个操作符 来说,上游可能是⼀个数据源,也可能是其他操作符,下游可能是最终的 观察者,也可能是另⼀个操作符,每⼀个操作符之间都是独⽴的,正因为 如此,所以可以对操作符进⾏任意组合,从⽽产⽣各种功能的数据管道。

有⼀系列⽤于产⽣Observable函数,这些函数有的凭空创 造Observable对象,有的根据外部数据源产⽣Observable对象,更多的是根 据其他的Observable中的数据来产⽣新的Observable对象,也就是把上游数 据转化为下游数据,所有这些函数统称为操作符。

二、操作符基础?

操作符其实就是解决某个具体应⽤问题的模式。当我们要⽤RxJS解决 问题时,⾸先需要创建Observable对象,于是需要创建类操作符;当需要 将多个数据流中的数据汇合到⼀起处理时,需要合并类操作符;当需要筛 选去除⼀些数据时,需要过滤类操作符;当希望把数据流中的数据变化为其他数据时,需要转化类操作符;⽽对数据流的处理可能引起异常,所以 为了让程序更加强壮,我们需要异常处理类操作符;最后,要让⼀个数据 流的数据可以提供给多个观察者,我们需要多播类操作符。

三、操作符的分类

  • 创建类(creation)
  • 转化类(transformation)
  • 过滤类(filtering)
  • 合并类(combination)
  • 多播类(multicasting)
  • 错误处理类(error Handling)
  • 辅助⼯具类(utility)
  • 条件分⽀类(conditional&boolean)
  • 数学和合计类(mathmatical&aggregate)

所谓创建类操作符,就是⼀些能够创造出⼀个Observable对象的⽅法,所谓“创造”,并不只是说返回⼀个Observable对象,因为任何⼀个操作 符都会返回Observable对象,这⾥所说的创造,是指这些操作符不依赖于 其他Observable对象,这些操作符可以凭空或者根据其他数据源创造出⼀ 个Observable对象。

“创建类”操作符就是数据河流的源 头,创建类操作符是没有上游的,它们是数据管道的起点。

四、创建同步数据流

1.create 直接调用Observable的构造函数,返回一个Observable对象

create是最简单的⼀个操作符,因为它的功能很简单,就是直接调⽤ Observable的构造函数据。

 const { Observable } = rxjs;
/*
  创建在订阅函数中发出 'Hello' 和 'World' 的 observable 。
*/
const hello = Observable.create(function (observer) {
  observer.next('Hello');
  observer.next('World');
});

// 输出: 'Hello'...'World'
const subscribe = hello.subscribe(val => console.log(val));

适合create的场景是真的没多少啊,一般都会有其他操作符来替代。

2.of 创建指定的数据流集合

利⽤of这个操作符可以轻松创建指定数据集合的Observable对象,⽐如,为了产⽣包含三个正整数的Observable对象,如果利⽤Observable的构 造函数,需要写⼀⼤堆的代码,但是如果使⽤of,产⽣数据流的代码可以 简化为⼀⾏。

const { of } = rxjs;
const source$ = of(1, 2, 3);
source$.subscribe(
  console.log,
  null,
  () => console.log('complete')
);

适合使⽤of的场合是已知不多的⼏个数据,想要把这些数据⽤ Observable对象来封装,然后就可以利⽤RxJS强⼤的数据管道功能来处 理,⽽且,也不需要这些数据的处理要有时间间隔,这就⽤得上of了。

3.range 指定范围

对需要产⽣⼀个很⼤连续数字序列的场景,就⽤得上range这个操作符 了,range的含义就是“范围”,只需要指定⼀个范围的开始值和长度,range 就能够产⽣这个范围内的数字序列。

const source$ = Observable.range(1, 100);

和of⼀样,range以同步的⽅式吐出数据,也就是100个数据依次⽆时 间间隔⼀⼜⽓全推给Observer,然后调⽤Observer的complete函数。

range的第⼀个参数不仅可以是整数,还可以是任何数字,样产⽣的数字序列是从1.5开始,每次递增1,⼀共吐出3个数据,产 ⽣的数据序列就是:1.5、2.5、3.5。

const source$ = Observable.range(1.5, 3)

4.generate 循环创建

generate类似⼀个for循环,设定⼀个初始值,每次递增这个值,直到 满⾜某个条件的时候才中⽌循环,同时,循环体内可以根据当前值产⽣数据。

const source$ = generate(
  2, // 初始值,相当于for循环中的i=2
  value => value < 10, //继续的条件,相当于for中的条件判断
  value => value + 2, //每次值的递增
  value => value * value // 产⽣的结果
);
source$.subscribe(console.log) // 4 16 36 64 

也可以⽤generate产⽣不限于数值序列的数据流,这⼀点 range 做不到的。

const { generate } = rxjs;
const source$ = generate(
  'x',
  value => value.length <= 3,
  value => value + 'x',
  value => value
);
source$.subscribe(console.log) // x xx xxx

在传统的JavaScript编程中,如果某个问题的解决⽅法是⽤⼀个for循环 产⽣的数据集合,那么搬到RxJS的世界,就适合于使⽤generate来产⽣⼀个 Observable对象。

5.repeat 重复数据的数据流

repeat的功能是可以重复上游Observable中的数据若⼲次。

const {  of } = rxjs;
const { repeat } = rxjs.operators;
const source$ = of('Repeat message');
const example$ = source$.pipe(repeat(3));
example$.subscribe(x => console.log(x)); // Repeat message X 3

根据字面意思很容易理解 repeat 就是重复执行,这里需要注意的是 source$example$ 是两个 Observable对象,在repeat的作⽤ 下,source$ 实际上被subscribe了3次,这3次 source$ 吐出的数据全部都变成了 example$吐出的数据。

如下图第三次也是重复前面的动作。

image.png

repeat只有在上游Observable对象完结之后才会重新订 阅,因为在完结之前,repeat也不知道会不会有新的数据从上游被推送下来。

为repeat只有在上游Observable对象完结之后才 会再次去subscribe这个对象,如果上游Observable对象永不完结,那repeat 也就没有机会去unsubscribe。为repeat的“重复”功能依赖于上游的完结时机,所以,使⽤repeat很 重要的⼀点,就是保证上游Observable对象最终⼀定会完结,不然使⽤ repeat就没有意义。

6.empty 是产⽣⼀个直接完结的Observable对象

const { empty } = rxjs;
const source$ = empty();
source$.subscribe(console.log) // 空

empty就是产⽣⼀个直接完结的Observable对象,没有参数,不产⽣任 何数据。

7.throwError 抛出错误

const { throwError } = rxjs;
const source$ = throwError(new Error('error'))
source$.subscribe(console.log) //  Uncaught Error: error

throw产⽣的Observable对象也是什么都不做,直接出错,抛出的错误 就是throw的参数。

需要注意的是在 rxjs6 之前 throwError 用的都是 throw 关键字, 我想改为 throwError 是因为 throw 是 js 的关键字吧 。

8.never 产⽣的 Observable 什么都不做

const { never } = rxjs;
const source$ = never()
source$.subscribe(console.log) // 空

never产⽣的 Observable对象就真的是什么都不做,既不吐出数据,也不完结,也不产 ⽣错误,就这样待着,⼀直到永远。而 empty 是直接完结的。

never、empty和throw单独使⽤没有意义,但是,在组合Observable对 象时,如果需要这些特殊的Observable对象,这三个操作符可以直接使⽤。

五、创建异步数据的Observable对象

1.interval 表产⽣数据的间隔毫秒数

在RxJS中,interval和timer这两个操作符类似于 Js 中的 setInterval 和 setTimeout。但其功能上并不完全⼀样。

interval接受⼀个数值类型的参数,代表产⽣数据的间隔毫秒数,返回的 Observable 对象就按照这个时间间隔输出递增的整数序列, 从0开始。

const { interval } = rxjs;
const source$ = interval(1000)
source$.subscribe(console.log) // 每隔1000毫秒输出1次 0-1-2-3-4-5...

interval不会主动调⽤下游的 complete,要想停⽌这个数据序列,就必须要做退订的动作。

若想让 interval 自定义开始可以利用别的操作符组合使用。

const { interval } = rxjs;
const { map } = rxjs.operators;
const source$ = interval(1000)
source$.pipe(
  map(x => x + 3)
).subscribe(console.log) // 3-4-5...

2.timer 在指定毫秒之后产⽣数据

const { timer } = rxjs;
const { map } = rxjs.operators;
const source$ = timer(3000)
source$.subscribe(console.log) // 3秒后输出 0

timer还⽀持第⼆个参数,如果使⽤第⼆个参数,那就会产⽣⼀个持续 吐出数据的Observable对象, 第⼆个参数指定的是各数据之间的时间间隔,从被订阅到产⽣第⼀个数据0的时间间隔,依然由第⼀个参数决定。简单的说就是第一个控制初始数据产生的开始,第二个参数控制开始之后的间隔时间。

const { timer } = rxjs;
const { map } = rxjs.operators;
const source$ = timer(4000,1000)
source$.subscribe(console.log) // ----0-1-2-3-4-5...

3.from 可把⼀切转化为Observable

它接受的参数 只要“像”Observable就⾏,然后根据参数中的数据产⽣⼀个真正的 Observable对象。“像”Observable的对象很多,⼀个数组就像Observable,⼀个不是数组 但是“像”数组的对象也算,⼀个字符串也很像Observable,⼀个JavaScript 中的generator也很像Observable,⼀个Promise对象也很像,所以,from可 以把任何对象都转化为Observable对象。

转化数组:

const { from } = rxjs;
const source$ = from([1, 2, 3])
source$.subscribe(console.log) // 1 2 3 

类数组也是可以的:

const { from } = rxjs; 
function toObservable() {
  return from(arguments);
}
const source$ = toObservable(1, 2, 3)
source$.subscribe(console.log) //  1 2 3 

在ES6中的generator:

function* generateNumber(max) {
  for (let i = 1; i <= max; ++i) {
    yield i;
  }
}
const source$ = from(generateNumber(5))
source$.subscribe(console.log) // 1 2 3 4 5

from把generator的结果塞给了产⽣的Observable对象。

字符串:

const { from } = rxjs;
const source$ = from('rxjs');
source$.subscribe(console.log) //r x j s

为在from的眼⾥,把输⼊参数都当做⼀个Iterable来看待,字符串abc 在from看来就和数组['a','b','c']没有区别。

也可以用于真正的 Observable。

const { from,of } = rxjs;
const another$ = of(1, 2, 3);
const source$ = from(another$)
source$.subscribe(console.log)

注意,上面的例子没有什么实际的意义,只是说明 from 也可以用于 Observable。

promise:

如果from的参数是Promise对象,那么这个Promise成功结束,from产⽣ 的Observable对象就会吐出Promise成功的结果,并且⽴刻结束

const { from, of } = rxjs;
const promise = Promise.resolve('first');
const source$ = from(promise);
source$.subscribe(
  console.log,
  error => console.log('catch', error),
  () => console.log('complete')
);
source$.subscribe() // first  complete

Promise对象虽然也⽀持异步操作,但是它只有⼀个结果,所以当 Promise成功完成的时候,from也知道不会再有新的数据了,所以⽴刻完结 了产⽣的Observable对象。

当Promise对象以失败⽽告终的时候,from产⽣的Observable对象也会 ⽴刻产⽣失败事件。如下:

const { from, of } = rxjs;
const promise = Promise.reject('reject');
const source$ = from(promise);
source$.subscribe(
  console.log,
  error => console.log('catch', error),
  () => console.log('complete')
);
source$.subscribe() // catch reject

4.fromEvent 为document元素绑定事件

fromEvent是DOM和RxJS世界的桥梁,产⽣Observable对象之后,就可 以完全按照RxJS的规则来处理数据。

fromEvent除了可以从DOM中获得数据,还可以从Node.js的events中获得数据,

const { fromEvent } = require('./rxjs')
const { EventEmitter } = require('events');

const emitter = new EventEmitter();
const source$ = fromEvent(emitter, 'msg');
source$.subscribe(
    console.log,
    error => console.log('catch', error),
    () => console.log('complete')
);
emitter.emit('msg', 1);
emitter.emit('msg', 2);
emitter.emit('another-msg', 'oops');
emitter.emit('msg', 3);
// 输出 1 2 3 

六、小结

数据流的创建是使⽤RxJS数据管道的第⼀步,只有获得数据流之后, 才可以发挥RxJS其他操作符的强⼤功能。

参考《深入浅出RxJs》——程墨