Rxjs入门指南(万文并茂)

990 阅读13分钟

pexels-s-migaj-747964.jpg rxjs本来入门就很恶心,我看了掘金上面很多博客主写这个rxjs,说什么先会用再去理解,我自己踩坑就是这样过来的,公司别人写的代码都是已经成熟的,我们一开始是不知道有啥用,就是问了也是大概知道这样用,但是没有了解最基本的概念,下次知道这样做在不同业务下还是会用错,我觉着快速入门一个工具库,一些基本概念还是要先知道,我们才能逐步去深入理解的。本文没有多高深的讲解,甚至有些浅显,本着能用图讲明白就不想废话了,从代码、图、简要概念去初步掌握rxjs的基本使用,个人认为,学习一切语言技术都是从代码出发,讲的再多,没有栗子(代码、相关辅助理解的工具),也是废话,代码搞不懂,那就结合代码看看图,把相关概念的理解捋顺

在《深入浅出Rxjs》这本书中,作者把rxjs中的对象形用为流(stream)。也就是书中所说的数据流Observable对象。把数据流理解为一条河流,数据就是河流中的水。而Rxjs命名流的方式称为芬兰式命名法。如source$。在学习rxjs前,我们从流的角度去理解一下回压这个概念吧!

一个传输管道如果管径变小了就会造成流动上游的淤积,因为变小的管道容不下当前水流的容量导致回压,如果在向服务器请求的场景下出现这种情况,就会导致数据响应回来非常慢。所以rxjs提供了一些操作符控制这个回压问题,分为有损回压控制(也就是下文的过滤类操作符,丢掉一点数据以达到当前数据能轻松通过)和无损回压控制(转化类操作符,把数据缓存起来,当时机合适时,把缓存的数据汇聚到一个数组或者Observable对象传给下游(也就是吐出数据))。

image.png

ReactiveX(包括Rxjs)诞生的主要目的是解决异步处理的问题,但并不表示Rx不适合同步数据处理。Rxjs(用JavaScript语言实现的Reative Extension)是微软发明的一个解决异步编程工具。本文将以Rxjs6版本对rxjs进行详细的学习。

1 函数式编程和响应式编程

Rxjs兼具函数式响应式两种编程特点。

  • 函数式编程:强调使用函数来解决问题的一种编程方式。

      函数式编程的要求:
      1.函数为必须为声明式。
      2.函数必须为纯函数:
      函数执行过程完全由输入参数决定,不会受参数之外的任何数据影响;
      不会修改任何外部的全局变量或传入的参数对象。
      3.数据不可变性:不修改原来数据,产生新的数据来作运算结果。           
    
// 函数为声明式
const double = (arr)=>arr.map((item:number)=>item*2);
const addOne = (arr)=>arr.map((item:number)=>item+1);

const arrPush1 = (data: Array<number>, addNum: number) => {
// 改变了外部变量,违反了纯函数的规则
    data.push(addNum);
    return data;
};
const arrPush2 = (data: Array<number>, addNum: number) => {
// 产生新数据
   return [...data, addNum];
};
const initData = [1, 2, 3];
const doubleArr1 = arrPush1(initData, 4);
const printArr1 = double(doubleArr1); // [2,4,6,8]
// initData = [1,2,3,4]

const doubleArr2 = arrPush2(initData, 4);
const printArr2 = double(doubleArr2); // [2,4,6,8,8]
// initData=[1,2,3]

// 函数的数据不可变性
  • 响应式编程:一种面向数据流变化传播、实现异步编程的编程范式。数据流可以接受一个,甚至多个。而响应式就是从数据流中过滤、组合等操作出你感兴趣的以生成一个新的数据流,还可以把一个流中的数据值映射到一个新的数据流中。简单地说,它的变化传播在于一个数据流经过一串操作转化为另一个数据流,然后分发给各个订阅者的过程。它的异步编程在于数据流被分发到各个订阅者前对数据进行特殊处理,订阅者根据数据处理后响应新的数据流(有待补充...)。

2 rxjs基础概念

2.1 Observable和Observer

  • Observable:可以被观察的对象,即可被观察者。
  • Observer:观察者。 两者之间的桥梁就是Observable对象的函数subscribe。有一天,我们发现《丛林大反攻》这部3d电影很火爆,看了一下简介,棕熊布哥的强健体型令人震撼,它在丛林狩猎地的战斗令人滑稽...不得不说挺吸引人的,而这部3d电影就类似被观察者Observable,但是我们用肉眼是体验不到那种真实感的,现在我们就需要借助3d眼镜这个subscribe了,而我们就是那个观察者Observer。好的,工具齐全了,我们现在可以出发去看电影嘞!

image.png

// 2.1
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/add/observable/of';

const dataStream$ = Observable.of(1,2,3);
dataStream$.subscribe(console.log);//1 2 3

在Rxjs中,rxjs编程是结合了观察者模式迭代者模式(数据的推、拉实现数据的传送)的。观察者模式刚才已经了解了,而rxjs中不需要主动从Observable中拉数据,而是通过subscribe实现数据的推送,这就是rxjs结合了观察者和迭代者模式。

// 2.2
const onSubscribe = (observer:any)=>{
    observer.next(1);
    observer.error('wrong!');// 出错就是终结了
    observer.complete();
};
const dataStream$ = new Observable(onSubscribe);
const theObserver = {
    next:(item:any)=>console.log(item),
    error:(err:any)=>console.log(err),
    complete:()=>console.log('no more Data'),
}
dataStream$.subscribe(theObserver);
// 1 wrong!

Observable对象只有三种状态:正常状态(next)、出错状态(error)、完结状态(complete)。完结状态只有一个,但是出错状态也可终结程序,如上面的2.2代码。

image.png

  • 退订Observable:unsubscribe,在Observable产生的事件,只有Observer通过subscribe订阅之后才会收到,在unsubscribe之后就不会再收到。Subscription 是一个对象,代表取消订阅,一般是配合Subject一起使用,该对象代表一个一次性资源,通常是一个 Observable 的执行。 Subscription 有一个重要的方法,unsubscribe,它不接受任何参数,只处理订阅持有的资源。
const dataStream$ = (observer:any)=>{
    let number = 1;
    const addNum = setInterval(()=>{
      observer.next(number++);
    },1000);
    return {
     unsubscribe:()=>{
       clearInterval(addNum);
     }
    }
};
const sub = new Observable(dataStream$).subscribe((data)=>
   console.log(data)
);
setTimeout(()=>{
   sub.unsubscribe();
},3000);
// 1 2 3

2.2 Hot Observable和Cold Observable

  • Hot Observable:有一个独立于Observable对象的生产者,这个生产者的创建与subscribe调用无关,subscribe调用只是Observer连接上"生产者",通俗理解过了这个村就没这店了。无论有没有subscribe订阅,事件始终都会发生,当Hot Observable有多个订阅者时,它与订阅者的关系是一对多的关系
const dataStream$ = interval(1000).pipe(take(3));
const subject = new Subject();
dataStream$.subscribe(subject);

subject.subscribe((data)=>console.log("observer1:",data));

setTimeout(()=>{
subject.subscribe((data)=>console.log("observer2:",data));
},2000);
// observer1: 0
// observer1: 1
// observer1: 2
// observer2: 2
  • Cold Observable:每一次的订阅都会产生一个新的生产者。只有subscribe订阅时才开始执行发射数据流的代码,Cold Observable与订阅者只能是一对一的关系,通俗理解不管你什么时候来我一直都在,当有多个不同的订阅者时,消息是重新完整发送的,他们各自的事件是独立的。
const dataStream$ = interval(1000).pipe(take(3));
dataStream$.subscribe((data)=>console.log("observer1:",data));
setTimeout(()=>{
  dataStream$.subscribe((data)=>console.log("observer2:",data));
},2000);
// observer1: 0
// observer1: 1
// observer1: 2
// observer2: 0
// observer2: 1
// observer2: 2

2.3 数据流与操作符

在rxjs中,创建一个数据流之后并不是直接通过subscribe接上一个Observer,而是先对这个数据流做一系列处理,然后才交给Observer的。比如说一个管道,数据从一个管道或多个管道流入,而管道中可能有多个交叉管道或管道有小有大,对数据进行过滤、抛弃掉了、数据来自多个数据源,最后Observer只需要处理能够走到终点的数据。组成数据管道的元素就是操作符

image.png

import {of} from 'rxjs';
import {filter,map} from 'rxjs/operators';

const dataStream$ = of(1,2,3,4,5,6,7,8);// Observable对象
 dataStream$.pipe(
   filter((x)=>x%2===0), // 过滤获取偶数
   map((x)=>x*2)   // 偶数的元素乘以2
 ).subscribe((data)=>console.log(data));// data也就是Observer获取已经处理好的数据
 // 4 8 12 16

上面的代码中detaStream$就是数据源头,而pipe里的filter和map就是管道中的操作符(等同于),对数据进行特定操作,data就是Observer可以获取已经处理的数据。

2.4 操作符分类

2.4.1 功能分类

操作符从功能上分类为:创建类操作符、合并类操作符、辅助类操作符、过滤类操作符、转化类操作符...

2.4.2 静态与实例分类

  • 静态操作符:不需要Observable实例就可以执行的函数。静态分类导入路径是包含observable,也就是静态类操作符是在rxjs的observable文件夹中。但是rxjs6静态操作符号是直接从rxjs中按需引入了,如of操作符:
import {of} from 'rxjs';

const dataStream$ = of(1,2,3);
dataStream$.subscribe((data: any)=>console.log(data));
// 1 2 3
  • 实例操作符:需要创建Observable对象才能调用的实例函数。实例操作符的导入路径是包含operator,实例操作符是在rxjs的operator文件夹中。如merge操作符:
import {of} from 'rxjs';
import {merge} from 'rxjs/operators';

const dataStream1$ = of(1,3,5);
const dataStream2$ = of(2,4,6);
const result$ = new Observable();
result$.pipe(merge(dataStream1$,dataStream2$)).subscribe(console.log);
// 1 3 5 2 4 6

3 创建类操作符

  • 创建类操作符:一些能够创建出一个Observable对象的方法,不依赖其他Observable对象,这些操作符可以凭空或其他数据源创造出一个Observable对象。

image.png

3.1 create

  • create:直接操作观察者
 const observable = Observable.create((observer:any)=>{
       observer.next('text');
 });
 observable.subscribe(console.log); // text

3.2 of

  • of:列举数据,可以轻松创建指定数据集合的Observable对象。

image.png

import { of } from 'rxjs';// rxjs6版本的按需引入

const dataStream$ = of(1);
dataStream$.subscribe(console.log); // 1

3.3 generate

  • generate:以循环方式产生一个数值范围内的数据。
import {generate} from 'rxjs';

// 循环以0到3范围内的数值,然后进行相应操作,每个数+1,每个数乘以2
generate(0,x=>x<3,x=>x+1,x=>x*2).subscribe(console.log);
// 0 2 4

image.png

3.4 range

  • range:产生一个范围内的数值。

image.png

import {range} from 'rxjs';

range(1,10).subscribe(console.log);
// 1 2 3 4 5 6 7 8 9 10

3.5 repeat和repeatWhen

  • repeat:重复数据流中的数据
import {of} from 'rxjs';
import {repeat} from 'rxjs/operators';

const dataStream$ = of('a','b');
dataStream$.pipe(repeat(3)).subscribe(console.log);
//a b a b a b

image.png

  • repeatWhen:在指定条件下重复数据流中的数据。
import { of, fromEvent } from 'rxjs';
import { repeatWhen } from 'rxjs/operators';

const dataStream$ = of(1,2);
const documentClick$ = fromEvent(document, 'click');
// 每点击一次屏幕就重刷一次数据
dataStream$.pipe(repeatWhen(() => documentClick$)).subscribe(data => console.log(data));

image.png

3.6 empty、never、throw

  • empty:不向观察者发送任何内容。用于调度完成的通知。
// RxJS v6+
import { empty } from 'rxjs';

// 输出: 'Complete!'
empty().subscribe({
  next: () => console.log('Next'),
  complete: () => console.log('Complete!')
});
  • never已弃,NEVER:一个不向观察者发射任何物品并且永远不会完成的Observable。
import { NEVER } from 'rxjs';
import { startWith } from 'rxjs/operators';
info() {
    console.log('Will not be called');
}
const result = NEVER.pipe(startWith(7));
result.subscribe(x => console.log(x), info, info);// 打印7
  • throw:产生直接出错的数据流。
import {throwError} from 'rxjs';

const error = throwError('出错了!');
error.subscribe({
  next: () => console.log('next'),
  error: (value) => console.log(`${value}`),// 打印出错了!
  complete: () => console.log('complete'),
});

3.7 from~系列的操作符

  • from:将各种其他对象和数据类型转换为Observable。
import {from} 'rxjs';

const dataStream$ = from([10, 20, 30]);
dataStream$.subscribe((data) => console.log(data));// 10 20 30

image.png

  • fromPromise:异步处理的交接。rxjs5已经取消这个功能了,可以直接使用from就可以实现
const promise = Promise.resolve('good');
const dataStream$ = from(promise);
dataStream$.subscribe(
  console.log,
  (err) => console.log(err),
  () => console.log('complete!') // good complete!
);
  • fromEvent:把DOM中的事件转化为Observable对象中的数据。
import { fromEvent } from 'rxjs';

const clicks = fromEvent(document, 'click');
clicks.subscribe(() => console.log('别点了,你点疼我了!'));
  • fromEventPattern:接受两个参数,分别对应产生的Observable对象被订阅和推定时的动作。
import {fromEventPattern} from 'rxjs';
import { EventEmitter } from 'events';

const emitter = new EventEmitter();
const addHandler = (handler) => {
  emitter.addListener('msg', handler);
};
const removeHandler = (handler) => {
  emitter.removeListener('msg', handler);
};
const subscription = fromEventPattern(addHandler, removeHandler).subscribe(
  console.log,
  (err) => console.log('catch', err),
  () => console.log('complete')
);
emitter.emit('msg', 'hello');
emitter.emit('msg', 'world');

subscription.unsubscribe();
emitter.emit('msg', 'end');
// hello world

3.8 ajax

网页应用主要数据源有两个:一个是网页中DOM事件,另一个就是通过AJAX获得的服务器资源。

// html代码
<div>
    <button id="getUsers">Get Login Users</button>
    <div id="text"></div>
</div>

// 基于angular中的周期,注意在视图初始化上实现视图逻辑渲染
import { ajax } from 'rxjs/ajax';
import { fromEvent } from 'rxjs';

ngAfterViewInit() {
    fromEvent(document.querySelector('#getUsers'), 'click').subscribe(() => {
      ajax.getJSON('https://api.github.com/users?per_page=5').subscribe((value: any) => {
        let str = '';
        const user = (value as Array<any>).forEach((item) => {
          str = str + item.login + ',';
        });
        document.querySelector('#text').innerHTML = str;
      });
    });
  }

image.png image.png 通过https://api.github.com/users?per_page=5通过这个链接获取到一个json文件,通过点击get Login Users按钮发出请求,取响应回来的login字段。

3.9 defer

  • defer:数据源头的Observable需要占用资源,像fromEvent和ajax这样的操作符,还需要外部资源。而defer就是推迟占用资源的方法使用。
import {ajax} from 'rxjs/ajax';
import {defer} from 'rxjs';

const observableFactory = ()=>ajax(url);
const source$ = defer(ObservableFactory);

4 合并类操作符

  • 合并类操作符:把多个数据流回合到一个数据流中。

image.png

image.png

4.1 concat&concatAll

  • concat:数据流连锁。
import {of,concat} from 'rxjs';

const source1$ = of('a','b');
const source2$ = of('x','y');
concat(source1$,source2$).subscribe(console.log);//a b x y

image.png

  • concatAll:收集 observables,当前一个Observables完成时才会去订阅下一个Observables。
// RxJS v6+
import { take, concatAll } from 'rxjs/operators';
import { interval, of } from 'rxjs/observable/interval';

const obs1 = interval(1000).pipe(take(5));
const obs2 = interval(500).pipe(take(2));
const obs3 = interval(2000).pipe(take(1));
// 发出3个 observables
const source = of(obs1, obs2, obs3);
// 按顺序订阅每个内部 obserable,前一个完成了再订阅下一个
const example = source.pipe(concatAll());
example.subscribe((val) => console.log(val));
// 0 1 2 3 4 0 1 0

4.2 merge和mergeAll

  • merge:将多个 observables 转换成单个 observable。
// RxJS v6+
import { mapTo } from 'rxjs/operators';
import { interval, merge } from 'rxjs';

// 每2.5秒发出值
const first = interval(2500);
// 每2秒发出值
const second = interval(2000);
// 每1.5秒发出值
const third = interval(1500);
// 每1秒发出值
const fourth = interval(1000);

// 从一个 observable 中发出输出值
const example = merge(first.pipe(take(2)), second.pipe(take(1)), third.pipe(take(3)), fourth.pipe(take(4)));
example.subscribe((val) => console.log(val));
// 打印值如下图所示

image.png

  • mergeAll:收集多个Observable,不等前一个完成才去控制后一个的,而是数据谁先谁打印。
import { from, interval} from 'rxjs';
import { mergeAll } from 'rxjs/operators';

const arr1 = ['a','b','c','d'];
const arr2 = ['e','f','g'];

const source1$ = interval(1000).pipe(map(n=>arr1[n]),take(4));
const source2$ = interval(3000).pipe(map(n=>arr2[n]),take(3));

const example = from([source1$, source2$]).pipe(mergeAll());
example.subscribe((val) => console.log(val));

// a b c e d f g

image.png

4.3 zip和zipAll

  • zip: 拉链式的合并,上游的数据流比作拉链的链齿,拉链的时候,一一对应合并,合并后得到的数据流是按照索引一一对应的。
import { of,zip } from 'rxjs';

const source1$ = of(1, 2, 3,4,5);
const source2$ = of('A', 'B', 'C','D');

zip(source1$, source2$).subscribe((val) => console.log(val));
// [1,'A'] [2,'B'] [3,'C'] [4,'D'] 

image.png

  • zipAll:
import { from, interval} from 'rxjs';
import { mergeAll } from 'rxjs/operators';

const source1$ = interval(1000).pipe(take(4));
const source2$ = interval(3000).pipe(take(3));

const example = from([source1$, source2$]).pipe(zipAll());
example.subscribe((val) => console.log(val));
// [0,0] [1,1] [2,2]

4.4 combineLatest、combineAll、withLatestFrom

  • combineLatest:从所有输入Observable对象中拿最后一次产生的数据(最新数据),然后把这些数据组合起来传给下游。注意地,combineLatest 直到每个 observable 都至少发出一个值后才会发出初始值。
import { combineLatest } from 'rxjs';

const source1$ = of('a', 'b', 'c');
const source2$ = of(1, 2, 3);
const source3$ = of('x', 'y');
combineLatest(source1$, source2$, source3$).subscribe(console.log);
// ['c',3,'x'] ['c',3,'y'] 
  • combineAll:当源 observable 完成时,对收集的 observables 使用。
import { from,of } from 'rxjs';
import { combineAll } from 'rxjs/operators';

const source1$ = of('a', 'b', 'c');
const source2$ = of(1, 2, 3);
const source3$ = of('x', 'y');
from([source1$, source2$, source3$]).pipe(combineAll()).subscribe(console.log);
// ['c',3,'x'] ['c',3,'y']
  • withLatestFrom:只要源 Observable 发出一个值,它就会 使用该值加上其他输入的最新值来计算公式 可观察对象,然后发出该公式的输出。
import { from,of } from 'rxjs';
import { withLatestFrom } from 'rxjs/operators';

const source1$ = of(100, 200, 300, 400, 500);
const source2$ = of(1, 2, 3, 4, 5);
source1$.pipe(withLatestFrom(source2$, (a, b) => a + b)).subscribe(console.log);
// 105 205 305 405 505

4.5 race

  • race:获取最先先发出值的 observable对象的数据流。
import { interval,race } from 'rxjs';
import { take } from 'rxjs/operators';

const source1$ = interval(1000).pipe(take(2));
const source2$ = interval(500).pipe(take(3));
const source3$ = interval(1500).pipe(take(1));
race(source1$, source2$, source3$).subscribe(console.log);
// 0 1 2

4.6 startWith

  • startWith:在源数据流发出前,先发出指定数据。
import { of } from 'rxjs';
import { startWith } from 'rxjs/operators';

const source1$ = of('a', 'b', 'c');
source1$.pipe(startWith('s')).subscribe(console.log);
// s a b c

image.png

4.7 forkJoin

  • forkJoin: 接受任意数量的输入可观察值,然后它将发出一个数组或一个对象,该对象或数组对象具有来自相应可观察变量的最后一个值
import { of,forkJoin } from 'rxjs';

const source1$ = of('a', 'b', 'c', 'd', 'e');
const source2$ = of('f', 'g', 'h', 'i', 'j');
const source3$ = of(1, 2, 3, 4);
forkJoin([source1$, source2$, source3$]).subscribe(console.log);
// ['e','j',4]

image.png

4.8 switch和exhaust

  • switch:切换的意思,已弃用,现在用的是switchAll,合并多个切换,下面以一个切换为栗子,每当上游产生一个Observable对象,switch都会立刻订阅最新的的Observable,用上新的舍弃旧的
import { interval } from 'rxjs';
import { map,switchAll, take } from 'rxjs/operators';

const sourcex$ = interval(1000).pipe(take(2));
const sourcey$ = sourcex$.pipe(
  map(x=>interval(1500).pipe(map(y=>x+':'+y),take(2)))
);
sourcey$.pipe(switchAll()).subscribe(console.log);
// 1:0 1:1

image.png

  • exhaust:耗尽的意思,在耗尽当前内部Observable的数据之前不会切换到下一个内部Observable对象。通过在先前内部 Observable 尚未完成之前删除内部 Observable,将高阶 Observable 转换为一阶 Observable。
import { interval } from 'rxjs';
import { map,switchAll, take } from 'rxjs/operators';

const sourcex$ = interval(1000).pipe(take(3),
    map(x=>interval(700).pipe(map(y=>x+':'+y),take(2)))
);
sourcex$.pipe(exhaust()).subscribe(console.log);
// 0:0 0:1 2:0 2:1 
// 被去掉了1:0 1:1

image.png 第一个Observable对象的数据流中第一个数据是在1s的时候吐出来的,也就是1s吐出的数据是0,接着就是吐出第二个Observable对象的数据流每0.7s吐出一个数据,吐完两个数据(0,1)需要1.4秒,也就是一共1s+1.4s=2.4s才能吐完,本来第一个Observable对象中的第二个数据1应该是2s的时候吐出的,由于第一个数据还没有完结,所以就删掉了这个数据,到了第三个数据的时候,也就是3s的时候第一个数据已经完结,此时可以进行第三个数据2的吐出(也就是如果到了特定时间吐出当前数据,但上一个数据没有吐完,当前时间要吐出的就被删掉)。

5 辅助类操作符

  • 辅助类操作符:

image.png

  • count:统计数据个数。
import { from, of } from 'rxjs';
import { concatAll, count } from 'rxjs/operators';

const source$ = from([of(4, 5, 6), of(1, 2, 3)]).pipe(concatAll(), count());
source$.subscribe(console.log);// 6
  • max和min:获取数据流中最大或最小的数据。
import { of } from 'rxjs';
import {  max, min } from 'rxjs/operators';

const sourceMax$ = of(-11, 2, 3000).pipe(max());
sourceMax$.subscribe(console.log); // 3000

const sourceMin$ = of(-20, 5, 1000).pipe(min());
sourceMin$.subscribe(console.log); // -20
  • every:判断所有数据是否满足某个判定条件。
import { of } from 'rxjs';
import { every } from 'rxjs/operators';

const source$ = of(2, 4, 3000, 8).pipe(every((a) => a % 2 === 0));
source$.subscribe(console.log); // true
  • reduce:对数据流中的数据进行规约操作。
import { interval } from 'rxjs';
import { reduce,take } from 'rxjs/operators';

const source$ = interval(100).pipe(
      take(10),
      reduce((acc, val) => acc + val, 0)
    ); // acc累加 val是数据
source$.subscribe(console.log); //0+1+2+3+4+5+6+7+8+9=45
  • find和findIndex:找到第一个满足某个判定条件的数据或索引
import { of } from 'rxjs';
import { find,findIndex } from 'rxjs/operators';

const source$ = of(1, 4, 0, -12, 9);
source$.pipe(find((a) => a < 0)).subscribe(console.log);// -12
source$.pipe(findIndex((a) => a < 0)).subscribe(console.log);// 3
  • isEmpty:判断数据流是否为空。
  • defaultIfEmpty:当数据流为空默认获取一个固定值。
import { of,from } from 'rxjs';
import { defaultIfEmpty, isEmpty} from 'rxjs/operators';

const source$ = of();
source$.pipe(isEmpty()).subscribe(console.log); //true

from([]).pipe(defaultIfEmpty('没有人上台,那就让我出场吧!')).subscribe(console.log);//没有人上台,那就让我出场吧!

6 过滤类操作符

image.png

6.1 filter

  • filter:过滤获取指定条件的数据
import { of } from 'rxjs';
import { filter } from 'rxjs/operators';

const source$ = of(1, -1, 0, 3, -2, 7);
source$.pipe(filter((a) => a < 0)).subscribe(console.log);// -1 -2

6.2 first

6.3 last

取符合条件的第一个数据和最后一个数据。

import { of } from 'rxjs';
import { first,last } from 'rxjs/operators';

const source$ = of(1, -1, 0, 3, -2, 7);
source$.pipe(first((a) => a < 0)).subscribe(console.log); //-1
source$.pipe(last((a) => a < 0)).subscribe(console.log); // -2

6.4 take系列

  • take:控制源Observable对象数据流最大输出数据量。
import { of } from 'rxjs';
import { take } from 'rxjs/operators';

const source$ = of('a', 'b', 'c', 'd');
source$.pipe(take(2)).subscribe(console.log); //a b

image.png

  • takeLast:在源完成才发出最后的takeLast限制的个数数据。

image.png

import { of } from 'rxjs';
import { takeLast } from 'rxjs/operators';

const source$ = of('a', 'b', 'c', 'd');
source$.pipe(takeLast(2)).subscribe(console.log); //c d
  • takeUntil:Observable数据流发出的数据直到遇到它所控制的数据发出就截止吐出数据。它的参数就是一个Observable类型的数据流。
import { of,interval } from 'rxjs';
import { takeLast,map } from 'rxjs/operators';

const arr1 = ['a', 'b', 'c', 'd', 'e', 'f', 'g'];
const arr2 = ['z'];
const result1$ = interval(500).pipe(map((n) => arr1[n]));
const result2$ = interval(2500).pipe(map((n) => arr2[n]));
result1$.pipe(takeUntil(result2$)).subscribe(console.log); //a b c d

image.png

  • takeWhile:遇到不满足当前条件的数据流就吐出满足条件的所有数据并结束吐出的数据。时空门:w3c-takeWhile
import { of,interval } from 'rxjs';
import { takeWhile,map } from 'rxjs/operators';

const arr = [2, 3, 4, 5, 6];
const source$ = interval(1000).pipe(map((n) => arr[n]));
source$.pipe(takeWhile((x) => x < 4)).subscribe(console.log); //2 3

image.png

6.5 skip系列

  • skip:跳过数据流的前几项。
import { of } from 'rxjs';
import { skip } from 'rxjs/operators';

const source$ = of('a', 'b', 'c', 'd', 'e');
source$.pipe(skip(3)).subscribe(console.log); //d e

image.png

  • skipLast:吐出跳过最后几项的前面所有项目。
import { of } from 'rxjs';
import { skipLast } from 'rxjs/operators';

 const source$ = of('a', 'b', 'c', 'd', 'e');
 source$.pipe(skipLast(2)).subscribe(console.log); //a b c
  • skipUntil:跳到某个数据发出时就可以开始吐出数据。
import { of } from 'rxjs';
import { skipUntil,map,take } from 'rxjs/operators';

const arr1 = ['a', 'b', 'c', 'd', 'e'];
const arr2 = ['x'];
const result1$ = interval(500).pipe(
  map((n) => arr1[n]),
  take(arr1.length)
);
const result2$ = interval(2000).pipe(
  map((n) => arr2[n]),
  take(1)
);
result1$.pipe(skipUntil(result2$)).subscribe(console.log); //d e

image.png

import { of } from 'rxjs';
import { skipWhile } from 'rxjs/operators';

const source$ = of(2, 3, 4, 5, 6);
source$.pipe(skipWhile((x) => x < 4)).subscribe(console.log); //4 5 6

image.png

6.6 throttleTime、debounceTime和throttle、debounce

🐂 基于时间控制流量:

  • throttleTime:在时间范围内,从上游传递给下游数据的个数。每个时间范围内保证在2000毫秒范围内只给下游唯一的一个数据。
import { of,interval } from 'rxjs';
import { throttleTime,take } from 'rxjs/operators';

const source$ = interval(1000).pipe(take(10));
source$.pipe(throttleTime(2000)).subscribe(console.log);// 0 3 6 9

image.png

为什么是0 3 6 9?因为source$要有数据throttleTime才会开始判断,1s吐出了数据0,此时throttleTime开始计时,到3s时,正常来说数据流source$会吐出0 1 2的,但是由于控制2s范围内只能吐出一个数据,而计时开始时已经有了一个数据0,到了3s也只能吐出0数据,1 2被丢弃了,由于3s此时source$是没有数据的,又要到4s的时候产生数据3,同理....

  • debounceTime:给定的时间范围要小于传递给下游的数据间隔。也就是要在给定的范围内不能有数据产生才会吐出数据。
import { of,interval } from 'rxjs';
import { debounceTime, filter,take } from 'rxjs/operators';

const source$ = interval(1000).pipe(take(10));
source$
  .pipe(
    filter((x) => x % 3 === 0),
    debounceTime(2000)
  )
  .subscribe(console.log); // 0 3 6 9

也是从source$有数据开始判断,1s+2s=3s时候可以吐出数据0了,2s内数据1 2都被过滤掉了,所以2~3s内是没有数据吐出的,此时就可以吐出在1s时就产生的数据,同理... 🐂 基于数据控制流量:

  • throttle:控制数据节流,在节流时间内不吐出数据,丢弃节流时间内产生的数据。
import { interval } from 'rxjs';
import { throttle,take } from 'rxjs/operators';

const source$ = interval(1000).pipe(take(10)); // 节流2秒后才发出最新值
source$.pipe(throttle((val) => interval(2000))).subscribe(console.log); // 0 3 6 9
  • debounce:只有在最后一次发送后再经过一秒钟,才会发出值,并抛弃在此之前的所有其他值。时空门:learn-rxjs-debounce
import { of,timer } from 'rxjs';
import { debounce } from 'rxjs/operators';

 const source$ = of('a', 'b', 'c', 'd', 'e');
 source$.pipe(debounce((val) => timer(1000))).subscribe(console.log); // e

6.7 audit、auditTime

auditTime:在节流时间内,throttle把第一个数据传给下游,audit是把最后一个数据传给下游。

import { of,interval } from 'rxjs';
import { auditTime,take } from 'rxjs/operators';

const source$ = interval(1000).pipe(take(10));
source$.pipe(auditTime(2000)).subscribe(console.log);// 2 5 8

audit:设置一个内置的Observable控制数据。

import { of,interval } from 'rxjs';
import { audit,take } from 'rxjs/operators';

const source$ = interval(1000).pipe(take(10));
source$.pipe(audit((val) => interval(2000))).subscribe(console.log); // 2 5 8

6.8 sample、sampleTime

  • sample:每2秒从最新的数据流中取样,但是如果最新产生的值是最后一个数据就不去样,如下栗子。
import { interval, sample, take } from 'rxjs';
import { sample } from 'rxjs/operators';

const source$ = interval(1000).pipe(take(10));
source$.pipe(sample(interval(2000))).subscribe(console.log); // 1 3 5 7
  • sampleTime:以周期性的时间间隔对源Observable进行采样。
import { from, interval, of } from 'rxjs';
import {concatAll,mapTo,sampleTime,take} from 'rxjs/operators';

const source1$ = interval(500).pipe(take(2), mapTo('A'));
const source2$ = interval(1000).pipe(take(3), mapTo('B'));
const source3$ = interval(500).pipe(take(3), mapTo('C'));
from([source1$, source2$, source3$]).pipe(concatAll()).subscribe(console.log);

6.9 distnct系列

  • distnct:含义不同,返回唯一的数据,去除重复数据。
import { of } from 'rxjs';
import {distinct} from 'rxjs/operators';

const source$ = of(1, 2, 2, 1, 3);
source$.pipe(distinct()).subscribe(console.log);// 1 2 3

image.png

  • distnctUntilChanged:比较相邻的数据,去掉相邻的重复数据
// 不带比较器情况
import { of } from 'rxjs';
import {distinctUntilChanged} from 'rxjs/operators';

const source$ = of(1, 2, 2, 1, 3);
source$.pipe(distinctUntilChanged()).subscribe(console.log); // 1 2 1 3

image.png

import { of } from 'rxjs';
import {distinctUntilChanged} from 'rxjs/operators';

interface Person {
  name: string;
  age: number;
}

const source$ = of<Person>(
{ name: '唐僧', age: 1000 }, 
{ name: '孙悟空', age: 500 }, 
{ name: '猪八戒', age: 500 }, 
{ name: '沙僧', age: 100 });
source$.pipe(distinctUntilChanged((a: Person, b: Person) => a.age === b.age)).subscribe(console.log); 
// {name: "唐僧", age: 1000}
// {name: "孙悟空", age: 500}
// {name: "沙僧", age: 100}
  • distnctUntilKeyChanged:以指定属性比较判断相邻属性,去掉属性相同的数据。
import { of } from 'rxjs';
import {distinctUntilKeyChanged} from 'rxjs/operators';

// 不带比较器
interface Person {
  name: string;
  age: number;
}

const source$ = of<Person>(
{ name: '唐僧', age: 1000 }, 
{ name: '孙悟空', age: 500 }, 
{ name: '猪八戒', age: 500 }, 
{ name: '沙僧', age: 100 });
source$.pipe(distinctUntilKeyChanged('age')).subscribe(console.log);
// {name: "唐僧", age: 1000}
// {name: "孙悟空", age: 500}
// {name: "沙僧", age: 100}
import { of } from 'rxjs';
import {distinctUntilKeyChanged} from 'rxjs/operators';

// 带比较器
interface Person {
  name: string;
  age: number;
}

const source$ = of<Person>(
{ name: '唐僧', age: 1000 }, 
{ name: '沙僧', age: 100 }, 
{ name: '孙悟空', age: 500 }, 
{ name: '猪八戒', age: 500 });
source$.pipe(
distinctUntilKeyChanged('name', 
(a: string, b: string) => a.substring(a.length - 1, a.length) === b.substring(b.length - 1, b.length))
).subscribe(console.log);
// {name: "唐僧", age: 1000}
// {name: "孙悟空", age: 500}
// {name: "猪八戒", age: 100}

6.10 ignoreElements

import { of } from 'rxjs';
import {ignoreElements} from 'rxjs/operators';

const source$ = of('别丢下我呀,我也要去看电影!');
source$.pipe(ignoreElements()).subscribe(
  (value) => console.log(value),
  (err) => console.log(err),
  () => console.log('就是要丢下你!')
);// 就是要丢下你!

6.11 elementAt

  • elementAt:发出指定索引的数据并完成。
import { of } from 'rxjs';
import {elementAt} from 'rxjs/operators';

const source$ = of(2, 3, '发出的就是我!', 5, 6);
source$.pipe(elementAt(2)).subscribe(console.log); // 发出的就是我!

6.12 single

  • single:如果数据流中有多个值的话,会发出错误。
import { of } from 'rxjs';
import {single} from 'rxjs/operators';

// 单个数据时
const source = from([1, 2, 3, 4, 5]).pipe(single((val) => val === 4));
source.subscribe((val) => console.log(val)); // 4

// 多个数据时
of(1, 2, 3, 4, 5, 6)
  .pipe(single())
  .subscribe(
    (value) => console.log(value),
    (err) => console.log('报错了!'),
    () => console.log('完成了')
  );// 报错了!

7 转化类操作符

image.png

7.1 map

  • map:将每个元素用映射函数产生新的数据。
import {of} from 'rxjs';
import {map} from 'rxjs/operators';

const source$ = of(1,3,8);
source$.pipe(
  map(x=>x*10),
).subscribe(console.log);// 10 30 80

7.2 mapTo

import {of} from 'rxjs';
import {mapTo} from 'rxjs/operators';

const source$ = of(1,2,3);
source$.pipe(
  mapTo('a'),
).subscribe(console.log);//a a a

image.png

7.3 pluck

import {of} from 'rxjs';
import {pluck} from 'rxjs/operators';

const source$ = of({'v':1},{'v':2},{'v':3});
source$.pipe(
  pluck('v'),
).subscribe(console.log);//1 2 3

image.png

7.4 window~

  • window:打开由内部 observable 指定的窗口。
import {of,timer} from 'rxjs';
import {window,tap,mergeAll} from 'rxjs/operators';

const source$ = timer(0,1000).pipe(take(6));

source$.pipe(
  window(interval(3000)),
  tap(()=>console.log('new window!')), // 拦截源上的每个发射并运行一个函数
  mergeAll()
).subscribe(console.log);
//new window 0 1 2
//new window 3 4 5
import {of,timer} from 'rxjs';
import {windowTime,tap,mergeAll} from 'rxjs/operators';

const source$ = timer(0,1000).pipe(take(6));// 延时0s间隔1s发送一个数据

source$.pipe(
  windowTime(3000),
  tap(()=>console.log('new window!')), // 拦截源上的每个发射并运行一个函数
  mergeAll()
).subscribe(console.log);
//new window 0 1 2
//new window 3 4 5
import {of,timer} from 'rxjs';
import {windowCount,tap,mergeAll} from 'rxjs/operators';

const source$ = timer(0,1000).pipe(take(10));

source$.pipe(
  windowCount(2),
  tap(()=>console.log('new window!')), // 拦截源上的每个发射并运行一个函数
  mergeAll()
).subscribe(console.log);
//new window 0 1
//new window 2 3
//new window 4 5
//new window 6 7
//new window 8 9
//new window
import {of,timer} from 'rxjs';
import {windowWhen,tap,mergeAll} from 'rxjs/operators';

const source$ = timer(0,1000).pipe(take(10));

source$.pipe(
  windowWhen(()=>interval(5000)),
  tap(()=>console.log('new window!')), // 拦截源上的每个发射并运行一个函数
  mergeAll()
).subscribe(console.log);
//new window 0 1 2 3 4
//new window 5 6 7 8 9
  • windowTogglewindowToggle(openings: Observable, closingSelector: function(value): Observable): Observable以 openings 发出为起始,以 closingSelector 发出为结束,收集并发出源 observable 中的值的 observable。根据渐增的定时器开关窗口。传送门:learn-rxjs-windowToggle
import {of,timer} from 'rxjs';
import {windowToggle,tap,mergeAll} from 'rxjs/operators';

const source$ = timer(0,1000).pipe(take(20));
const toggle = interval(5000);

source$.pipe(
  windowToggle(toggle,val=>interval(val*1000)),
  tap(()=>console.log('new window!')), // 拦截源上的每个发射并运行一个函数
  mergeAll()
).subscribe(console.log);
//new window
//new window 10
//new window 15 16

toggle就是设置一个5s的定时器,也就是每5s发出一个Observable,注意这个val是从0开始的,当到5s的时候val为0就没有持续时间所以只打印new window就结束,此时第二个5s开始,然后打印了new window然后此时时间(产生了0-9的数据)持续1*1000=1s所以打印10,第三个5s还剩余4s,因为刚才持续了1s,所以到第三个5s时此时时间(产生了0-14的数据)持续2*1000s=2s就打印了15 16....

7.5 buffer~

  • buffer:指定在当前数据流的条件下才会吐出原数据流的数据。
import {of} from 'rxjs';
import {interval,buffer,take,map} from 'rxjs/operators';

const arr1 = ['a','b','c','d','e','f','g','h','i'];
const arr2=['B','B','B'];

const source$ = interval(1000).pipe(take(9),map((n)=>arr1[n]));
const buffer$ = interval(3000).pipe(map((n)=>arr2[n]),take(3));

source$.pipe(
  buffer(buffer$),
).subscribe(console.log);
//['a','b']
//['c','d','e']
//['f','g','h']

上面的结果为什么不是a b c d e f g h i?因为到3秒的时候source$产生数据时就已经开始吐数据了,所以在3s 6s 9s时刻分别取不到c f i的数据。

  • bufferCount:直到指定大小bufferSize就清除缓冲区吐出数据,开始新一轮缓冲区,缓冲区有指定时间间隔。传送门:w3c-rxjs-bufferCount
import {of} from 'rxjs';
import {interval,bufferCount,take,map} from 'rxjs/operators';

const arr = ['a','b','c','d','e','f','g','h','i'];
const source$ = interval(1000).pipe(take(9),map((n)=>arr[n]));
source$.pipe(
  bufferCount(3,2),
).subscribe(console.log);
//  ["a", "b", "c"]
//  ["c", "d", "e"]
//  ["e", "f", "g"]
//  ["g", "h", "i"]
//  ["i"]

image.png

  • bufferTime:收集发出的值,直到经过了提供的时间才将其作为数组发出
import {of} from 'rxjs';
import {interval,bufferTime,take,map} from 'rxjs/operators';

const source$ = interval(500).pipe(take(10));
source$.pipe(
  bufferTime(2000),
).subscribe(console.log);
// [0, 1, 2]
// [3, 4, 5, 6]
// [7, 8, 9]
  • bufferToggle:两个参数,一个是开启缓冲区的并清除之前的数据,另一个是缓冲区持续时间然后就关闭。
import {of} from 'rxjs';
import {interval,bufferToggle,take,map} from 'rxjs/operators';

const source$ = interval(1000).pipe(take(20));
const startInterval = interval(5000);// 每5秒开启一次缓冲区

source$.pipe(
  bufferToggle(
    startInterval,
    val=>{
      console.log(`新的缓冲区${val}`);
      return interval(3000);// 缓冲区持续3s
    }
  )
).subscribe(console.log);
// 新的缓冲区0 [4, 5, 6, 7]
// 新的缓冲区1 [9, 10, 11, 12]
// 新的缓冲区2 [14, 15, 16, 17]
// 新的缓冲区3 [19]
  • bufferWhen:收集值,直到关闭选择器发出值才发出缓冲的值。
import {of,interval} from 'rxjs';
import {bufferWhen,take,map} from 'rxjs/operators';

const arr = ['b','c','d','e','f','g'];
const arr1 = ['s'];
const source$ = interval(1000).pipe(take(6),map(n=>arr[n]));
const startInterval = ()=>interval(4000).pipe(take(1),map(n=>arr1[n]));

source$.pipe(
  bufferWhen(startInterval)
).subscribe(console.log);
// ["b", "c", "d"]
// ["e", "f", "g"]

image.png

7.6 ~Map

  • concatMap:将值映射成内部 observable,并按顺序订阅和发出。
import { of,interval } from 'rxjs';
import {concatMap,take} from 'rxjs/operators';

const source1$ = of(1,3,5);
source1$.pipe(
  concatMap(i=>interval(1000).pipe(take(3)))
).subscribe(console.log);
// 0 1 2
// 0 1 2
// 0 1 2
  • mergeMap:将每个源值投影到一个 Observable 中,将其合并到输出中 可观察的。 🐱 flatMap 是 mergeMap 的别名!

🐱 如果同一时间应该只有一个内部 subscription 是有效的,请尝试 switchMap

🐱 如果内部 observables 发送和订阅的顺序很重要,请尝试 concatMap

import { of,interval } from 'rxjs';
import {mergeMap,take,map} from 'rxjs/operators';

const source$ = of('a', 'b', 'c');
const result = source$.pipe(
     mergeMap(x => interval(1000).pipe(take(3),map(i => x+i))),
);
result.subscribe(x => console.log(x));
  • switchMap:将每个源值投影到一个 Observable,将其合并到输出 Observable中,仅从最近投影的 Observable 发出值。传送门:w3c-rxjs-switchMap
import { of } from 'rxjs';
import {switchMap} from 'rxjs/operators';

const switched = of(1, 2, 3).pipe(switchMap((x: number) => of(x, x ** 2, x ** 3)));// x的2次幂和x的3次幂
switched.subscribe(x => console.log(x));
// 1 1 1
// 2 4 8
// 3 9 27
  • exhaustMap:映射成内部 observable,忽略其他值直到该 observable 完成。传送门:learn-rxjs
import { of } from 'rxjs';
import {interval,exhaustMap,take} from 'rxjs/operators';

const first$ = interval(1000).pipe(take(10));
const second$ = interval(1000).pipe(take(2));

first$.pipe(exhaustMap(val=>{
  console.log(`${val}interval`);
  return second$;
})).subscribe(console.log);
// 0interval 0 1
// 3interval 0 1
// 6interval 0 1
// 9interval 0 1

7.7~scan

  • scan:类似reduce,在源 Observable 上应用累加器函数,并返回每个中间结果以及可选的seed值。
import { of } from 'rxjs';
import {scan} from 'rxjs/operators';

const source$ = of(1,3,5);
// 0就是seed值
source$.pipe(scan((acc,curr)=>acc+curr,0)).subscribe(console.log);// 1 4 9
  • mergeScan:在源 Observable 上应用累加器功能 累加器函数本身返回一个 Observable,然后每个中间 返回的 Observable 合并到输出 Observable 中。传送门:w3c-rxjs-mergeScan
import { fromEvent, of } from 'rxjs';
import { mapTo, mergeScan } from 'rxjs/operators';

const click$ = fromEvent(document, 'click');
const one$ = click$.pipe(mapTo(1));
const seed = 0;
const count$ = one$.pipe(
mergeScan((acc, one) => of(acc + one), seed),
);
count$.subscribe(x => console.log(x));

8 异常错误处理

在Javascript中,try/catch只支持同步异常处理,不适用于异步操作,而回调函数传递错误异常的缺陷,还有Promise处理异常的局限,都可用rxjs来解决。

  • catch:这个是在js和rxjs以前的版本的说法,在rxjs6+版本中,是用catchError来取代catch。在深入浅出Rxjs中,可通俗为恢复数据流,它是捕获上游产生异常的错误。如果单纯用observer来直接可观察中的err属性方法,会直接报错,数据流被截断,而使用catchError捕获数据流中的错误,但是不会报错,会直接延续数据流,拼上catchError内部的Observable。
import { of, range } from 'rxjs';
import { catchError, map } from 'rxjs/operators';

const source$ = range(1, 10);
const error$ = source$.pipe(
  map((val) => {
    if (val === 4) {
      throw new Error('unlucky number 4');
    }
    return val;
  }),
  catchError((err) => of('I', 'II', 'III'))
);
error$.subscribe((val) => console.log(val));

// 1 2 3 I II III
  • retry:当上游产生错误时进行重试,也就是让上游Observable重新走一遍。一般retry结合catchError来使用,retry只是增加成功结果的概率,并不能保证一定成功。如果retry重试了它限制的次数还是失败的,就会试着走catchError了。
import { of, range } from 'rxjs';
import { catchError, map,retry } from 'rxjs/operators';

const source$ = range(1, 10);
const error$ = source$.pipe(
  map((val) => {
    if (val === 4) {
      throw new Error('unlucky number 4');
    }
    return val;
  }),
  retry(2),
  catchError((err) => of('I', 'II', 'III'))
);

error$.subscribe((val) => console.log(val));
// 1 2 3 1 2 3 1 2 3 I II III
  • retryWhen:当上游产生错误时进行重试。因为retry如果一检测到上游下错误时,就会立即重试,而retryWhen实现的是延时重试。
import { interval, of,  timer } from 'rxjs';
import { delayWhen, map, retryWhen, tap } from 'rxjs/operators';

const source$ = interval(1000);
const example = source$.pipe(
  map((val) => {
    if (val > 5) {
      throw val;
    }
    return val;
  }),
  retryWhen((error) =>
    error.pipe(
      tap((val) => console.log(`value${val}`)),
      delayWhen((val) => timer(val * 1000)) // 5秒重启
    )
  )
);
example.subscribe((val) => console.log(val));
// 0 1 2 3 4 5 value6 
// 重启...
  • finally:无论是否出错都要进行的操作。

9 多播

播放通过类型Subject实现,根据Observable和Observer的关系,就是前者在播放内容,后者在收听内容。播放形式分为三种:

  • 单播:只有一个人可以收到播放内容。
  • 广播:在教室大喊,教室中所有人都可以听到。
  • 多播(multicast):让一个数据流的内容被多个Observer订阅。同一个群体,指定有多个人听到播放内容,有部分听不到。 🐂 Subject兼具ObservableObserver的性质。
import { Subject } from 'rxjs';
import {  map } from 'rxjs/operators';

const subject = new Subject<any>();
// subject充当Observerable
subject.pipe(map((x) => x * 2)).subscribe(
  (value) => console.log(value),
  (err) => console.log(err),
  () => console.log('complate')
);
// subject充当observer
subject.next(1);
subject.next(2);
subject.next(3);
subject.complete();

// 2 4 6 complete

🐂 Subject不能重复使用,Subject对象是不能重复使用的,所谓不能重复使用,就是一个Subject如果调用了complete或error函数,它作为Observable的生命周期就结束了。因为Subject不是Cold Observable,所以每次的调用不会重建Observable。

import { Subject } from 'rxjs';

const subject = new Subject();
    subject.subscribe(
      (val) => console.log('observer 1 data:' + val),
      (err) => console.log('observer 1 err:' + err),
      () => console.log('observer 1 complete')
    );
    subject.next(1);
    subject.next(2);
    subject.complete();
    subject.next(3);
    subject.subscribe(
      (val) => console.log('observer 2 data:' + val),
      (err) => console.log('observer 2 err:' + err),
      () => console.log('observer 2 complete')
    );
    // observer 1 data:1
    // observer 1 data:2
    // observer 1 complete
    // observer 2 complete 

🐂 Subject可以有多个上游。

import { interval, Subject } from 'rxjs';
import { mapTo,take } from 'rxjs/operators';

const source1$ = interval(1000).pipe(mapTo('a'), take(2));
const source2$ = interval(1000).pipe(mapTo('b'), take(2));
const subject = new Subject();

source1$.subscribe(subject);
source2$.subscribe(subject);

subject.subscribe((val) => console.log(val));// a b a

为什么不是a b a b?因为source1数据流由take控制突出数量,吐出两个数据之后就调用下游的complete,此时subject的生命周期已经结束了,所以后面下游就不会吐出数据了。

🐂 Suject处理错误异常,Subject可以把上游数据传给下游,也可以把上游的错误传给下游,假如Subject有多个Observer的场景,只要有一个Observer出错,而没有被下游的err吞掉(处理掉),其他所有的Observer都会受到影响。

import { of,interval, Subject } from 'rxjs';
import { take } from 'rxjs/operators';

const source$ = interval(1000).pipe(take(10));
const subject = new Subject();
source$.subscribe(subject);
subject
  .pipe(
    map((val) => {
      if (val === 4) {
        throw new Error('我难听,咋的?开战?');
      }
      return val;
    })
    // catchError(() => of('我让你进行不下去!'))
  )
  .subscribe(console.log);

subject.subscribe((val) => console.log(val));
// 0 0 1 1 2 2 3 3 4报错了

解决方法一:用catchError替代出错的Observer并完结当前的Observer,其他没有异常的Observer将正常进行。

import { of,interval, Subject } from 'rxjs';
import { take,catchError } from 'rxjs/operators';

const source$ = interval(1000).pipe(take(10));
const subject = new Subject();
source$.subscribe(subject);

subject
  .pipe(
    map((val) => {
      if (val === 4) {
        throw new Error('我难听,咋的?开战?');
      }
      return val;
    })
    catchError(() => of('我让你进行不下去!'))
  )
  .subscribe(console.log);

subject.subscribe((val) => console.log(val));

解决方法二:对于每一个Observer都需要在下游对err进行处理,也就是调用err属性方法。

import { of,interval, Subject } from 'rxjs';
import { take } from 'rxjs/operators';

const source$ = interval(1000).pipe(take(10));
const subject = new Subject();
source$.subscribe(subject);

subject
  .pipe(
    map((val) => {
      if (val === 4) {
        throw new Error('我难听,咋的?开战?');
      }
      return val;
    })
  )
  .subscribe(
  (val)=>concole.log(val),
  err=>concole.log(err),
  ()=>console.log('complete!'));

subject.subscribe(
    (val)=>concole.log(val),
    err=>concole.log(err),
    ()=>console.log('complete!'));

9.1 多播类操作符号

  • multicast:选取Subject对象进行多播。实例操作符,能够以上游的Observable为数据源产生一个新的HotObservable对象。 connectrefCount都是ConnectableObservable对象中的方法属性。connect可以控制ConnectableObservable去订阅上游的时机,实际上,还要考虑退订上游的时机,如果不退订的话,可能导致资源泄露。refCount作用是当observer数量大于1时订阅上游,当Observer数量减少为0时退订上游。
import { of,interval, Subject,ConnectableObservable } from 'rxjs';
import { take,multicast } from 'rxjs/operators';

const coldSource$ = interval(1000).pipe(take(3));
const tick$ = coldSource$.pipe(multicast(() => new Subject())) as ConnectableObservable<number>;
tick$.subscribe((val) => console.log(val));
setTimeout(() => {
  tick$.subscribe((val) => console.log(val));
}, 1500);
tick$.connect();
// 0 1 1 2 2
import { of,interval, Subject,ConnectableObservable } from 'rxjs';
import { take,multicast } from 'rxjs/operators';

const coldSource$ = interval(1000).pipe(take(3));
const tick$ = coldSource$.pipe(
  multicast(() => new Subject()),
  refCount()
) as ConnectableObservable<number>;
tick$.subscribe((val) => console.log(val));
setTimeout(() => {
  tick$.subscribe((val) => console.log(val));
}, 1500);
// 0 1 1 2 2

如果不执行ConnectableObservable中connect方法,这里是没有数据吐出的,为什么上面吐出的数据是0 0 1 1 2 2?因为第二个Observer是1.5s后才产生数据的,这时候Observer是没有收到0。

multicast除了第一个参数指定Subject对象或者指定一个产生Subject对象的工厂方法,multicast还有第二个参数selector,这个参数(是一个函数)是可选的,只有不使用第二个参数selector的情况下,multicast才返回拥有connect和refCount这两个神奇方法的ConnectableObservable对象,一旦使用了第二个参数selector,等于告诉multicast:我有一个比ConnectableObservable更合适的Observable,就不要返回默认的ConnectableObservable对象了,用我这个selector函数来产生Observable对象吧(《深入浅出Rxjs》10.4 支持多播操作符的一段解析)。

selector函数有一个shared,如果multicast的第一个参数是Subject对象,shared就是这个对象;如果multicast第一个参数是一个产生Subject对象的工厂方法,shared就是这个方法产生的Subject对象。

import { interval,ConnectableObservable } from 'rxjs';
import { publish, tap } from 'rxjs/operators';

// 每1秒发出值
const source = interval(1000).pipe(take(2));
const example = source.pipe(
  // 副作用只会执行1次
  tap(() => console.log('Do Something!')),
  // 不会做任何事直到 connect() 被调用
  publish()
) as ConnectableObservable<any>;
const subscribe = example.subscribe((val) => console.log(`Subscriber 1: ${val}`));
const subscribeTwo = example.subscribe((val) => console.log(`Subscriber 2: ${val}`));
// 5秒后调用 connect,这会使得 source 开始发出值
setTimeout(() => {
  example.connect();
}, 3000);
//Do Something! Subscriber 1: 0 Subscriber 2: 0
//Do Something! Subscriber 1: 1 Subscriber 2: 1

看看上面代码,是不是与multicast操作符的作用有类似,而coldSource$.pipe(publish(), refCount())等价于coldSource$.pipe(multicast(new Subject()), refCount()),再看看下面的代码。

import {  ConnectableObservable, interval } from 'rxjs';
import { refCount, publish} from 'rxjs/operators';

const coldSource$ = interval(1000).pipe(take(3));
const tick$ = coldSource$.pipe(publish(), refCount()) as ConnectableObservable<number>;
tick$.subscribe((val) => console.log(val));
setTimeout(() => {
  tick$.subscribe((val) => console.log(val));
}, 1500);
// 0 1 1 2 2
  • share:在多个订阅者间共享源 observable。 coldSource$.pipe(publish(), refCount())等价于coldSource$.pipe(share()),是不是很简单。 RxJS的share源代码:
Observable.prototype.share = function share(){
      this.multicast(()=>new Subject()).refCount();
}
import { interval } from 'rxjs';
import { share, publish} from 'rxjs/operators';

const coldSource$ = interval(1000).pipe(take(3));
const tick$ = coldSource$.pipe(share());
tick$.subscribe((val) => console.log(val));
setTimeout(() => {
  tick$.subscribe((val) => console.log(val));
}, 1500);
// 0 1 1 2 2
  • publishLast:只取多播数据流中最后一个数据。
import { interval,} from 'rxjs';
import {  publishLast, refCount, take } from 'rxjs/operators';

const source$ = interval(1000).pipe(take(3));
const tick$ = source$.pipe(publishLast(), refCount());
tick$.subscribe((val) => console.log(val));
setTimeout(() => {
  tick$.subscribe((val) => console.log(val));
}, 5000);
// 2 2
  • publishReplay:对数据流中给定数量的数据进行多播。对多播数据进行缓存,如果publishReplay没有参数的话,默认最大缓存大小,容易会造成内存泄漏,所以一般设置一个缓存大小。
import { interval,} from 'rxjs';
import {  publishReplay, refCount, take, tap } from 'rxjs/operators';

const source$ = interval(1000).pipe(
  take(3),
  tap((x) => console.log('source', x))
);
const tick$ = source$.pipe(publishReplay(2), refCount());
tick$.subscribe((val) => console.log('observer 1:', val));
setTimeout(() => {
  tick$.subscribe((val) => console.log('observer 2:', val));
}, 5000);
// source 0 observer 1: 0
// source 1 observer 1: 1
// source 2 observer 1: 2
// observer 2:1 
// observer 2:2

tap主要作用是为了检测是否调用了两次source$,从结果上看只是调用了一次,为什么Observer2输出只有1 2没有0?因为缓存大小最大为2,取的是倒数2个数据。

  • publishBehavior:拥有默认数据的多播。
import { interval} from 'rxjs';
import {  publishBehavior, refCount, take} from 'rxjs/operators';

const source$ = interval(1000).pipe(take(3));
const tick$ = source$.pipe(publishBehavior(-1), refCount());
tick$.subscribe((val) => console.log('observer 1:', val));
setTimeout(() => {
  tick$.subscribe((val) => console.log('observer 2:', val));
}, 2500);
// observer 1:-1
// observer 1: 0
// observer 1: 1
// observer 2: 1
// observer 1: 2
// observer 2: 2

没有到1s的时候observer 1吐出默认数据-1,observer1到了1s默认数据改为0并吐出数据0,到了2s时候默认数据改为1并吐出数据1,到了2.5s的时候observer 2吐出默认数据1,到了3s默认数据改为2并observer 1吐出数据2,到了5s的时候observer2吐出默认数据2。

9.2 扩充形态BehaviorSubject、ReplaySubject、AsyncSubject

10 Scheduler

Scheduler可以充当调度器,控制数据流中数据信息的推送节奏。

import { range} from 'rxjs';

console.log('before subscribe!');
const source$ = range(1,3).subscribe(
  val=>console.log('data:',val),
  err=>console.log('err:',err),
  ()=>console.log('complete!')
)
console.log('after subscribe!');
// before subscribe!
// data: 1
// data: 2
// data: 3
// complete!
// after subscribe!

上下代码对比一下子:加了asapScheduler(as soon as possible 调度器,尽可能快),能有多快就有多快,rxjs是处理异步的利器,两个bofore和after的console在source订阅前就已经加入事件循环了。使用asap这样的Scheduler,目的是提高整体的感知性能,而不是为了缩短单独一个数据流的执行时间。

import { range,asapScheduler} from 'rxjs';

console.log('before subscribe!');
const source$ = range(1,3,asapScheduler).subscribe(
  val=>console.log('data:',val),
  err=>console.log('err:',err),
  ()=>console.log('complete!')
)
console.log('after subscribe!');
// before subscribe!
// after subscribe!
// data: 1
// data: 2
// data: 3
// complete!
  • 支持Scheduler的操作符两种:普通的创建或者组合Observable对象的操作符添加Scheduler可选参数(也就是上面例子中在range中操作Scheduler),另一种是observeOn和subscribeOnobserveOn:Observable对象产生出一个新的Observable对象出来,让这个新的Observable对象吐出的数据由指定的Scheduler来控制。observeOn在数据流管道接近末尾的位置使用,最好就是在调用subscribe之前。
import { range,asapScheduler} from 'rxjs';
import {  observeOn } from 'rxjs/operators';

console.log('before subscribe!');
const source$ = range(1,3);
const asapSource$ = source$.pipe(observeOn(asapScheduler));
asapSource$.subscribe(
  val=>console.log('data:',val),
  err=>console.log('err:',err),
  ()=>console.log('complete!')
)
console.log('after subscribe!');
// before subscribe!
// after subscribe!
// data: 1
// data: 2
// data: 3
// complete!

subscribeOn:您可以使用它来确定特定 Observable 订阅时将使用哪种调度程序。调度程序控制可观察流向观察者的发射速度和顺序。

import { of,asyncScheduler} from 'rxjs';
import {  subscribeOn,merge } from 'rxjs/operators';

const a = of(1, 2, 3, 4).pipe(subscribeOn(asyncScheduler));
const b = of(5, 6, 7, 8, 9);
a.pipe(merge(b)).subscribe(console.log);// 5 6 7 8 9 1 2 3 4

#参考资料

1.《深入浅出Rxjs》 -作者程墨 著

2.# 什么是响应式编程,为什么使用它?

3.# Rxjs Marbles-文中部分操作符图片来源

4.# 响应式编程(Reactive Programming)介绍

5.# Cold Observable 和 Hot Observable

6.# Hot Observable 和 Cold Observable的联系与区别

7.# w3cschool-rxjs-文中部分操作符图片或代码来源

8.# Rxjs官方文档

9.# 学习Rxjs的一个网站