RxJS

发现一个网站讲的好详细

1. 简介

RxJS 是一个库,它通过使用 observable 序列来编写异步和基于事件的程序。它提供了一个核心类型 Observable,附属类型 (Observer、 Schedulers、 Subjects) 和受 [Array#extras] 启发的操作符 (map、filter、reduce、every, 等等),这些数组操作符可以把异步事件作为集合来处理。

可以把 RxJS 当做是用来处理事件的 Lodash

ReactiveX 结合了 观察者模式迭代器模式使用集合的函数式编程,以满足以一种理想方式来管理事件序列所需要的一切。

在 RxJS 中用来解决异步事件管理的的基本概念是:

  • Observable (可观察对象): 表示一个概念,这个概念是一个可调用的未来值或事件的集合。
  • Observer (观察者): 一个回调函数的集合,它知道如何去监听由 Observable 提供的值。
  • Subscription (订阅): 表示 Observable 的执行,主要用于取消 Observable 的执行。
  • Operators (操作符): 采用函数式编程风格的纯函数 (pure function),使用像 mapfilterconcatflatMap 等这样的操作符来处理集合。
  • Subject (主体): 相当于 EventEmitter,并且是将值或事件多路推送给多个 Observer 的唯一方式。
  • Schedulers (调度器): 用来控制并发并且是中央集权的调度员,允许我们在发生计算时进行协调,例如 setTimeoutrequestAnimationFrame 或其他。

常见的异步行为

  • ajax
  • 定时器
  • 事件

常见的 优化异步回调的方式

  • Promise
  • async/await

什么是函数式编程

具体解释见 — https://segmentfault.com/a/1190000008794344

简单说,"函数式编程"是一种 "编程范式"(programming paradigm),也就是如何编写程序的方法论。

它属于 "结构化编程" 的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用。举例来说,现在有这样一个数学表达式:

const result = (5 + 6) - 2 * 3;

传统的过程式编程,可能这样写:

var a = 5 + 6;
var b = 1 * 3;
var c = a - b;

函数式的写法:

const add = (a, b) => a + b
const mul = (a, b) => a * b
const sub = (a, b) => a - b

const result = sub(add(5, 6), mul(2, 3))

我们把每个运算包成一个个不同的函数,并且根据这些函数组合出我们要的结果,这就是最简单的函数式编程。

函数特性

  • 一定会有返回值
  • 不能产生副作用(纯函数)

纯函数的逻辑不能改变数据源 比如

const arr = [1, 2, 3, 4, 5];
arr.slice(0, 3);
console.log(arr); // arr不变,slice就是个纯函数
arr.splice(0, 3, 2);
console.log(arr); // arr改变,splice是不纯的

rxjs初体验

控制点击频率,每次点击至少间隔2秒

原生实现:

let count = 0;
let rate = 1000;
let lastClick = Date.now() - rate;
document.addEventListener('click', () => {
  if (Date.now() - lastClick >= rate) {
    console.log(`Clicked ${++count} times`);
    lastClick = Date.now();
  }
});

rxjs:

import { fromEvent } from 'rxjs';
import { throttleTime, scan } from 'rxjs/operators';

fromEvent(document, 'click')
  .pipe(
    throttleTime(1000),
    scan(count => count + 1, 0)
  )
  .subscribe(count => console.log(`Clicked ${count} times`));

累加每次点击的clientX值

let count = 0;
const rate = 1000;
let lastClick = Date.now() - rate;
document.addEventListener('click', event => {
  if (Date.now() - lastClick >= rate) {
    count += event.clientX;
    console.log(count);
    lastClick = Date.now();
  }
});

rxjs:

import { fromEvent } from 'rxjs';
import { throttleTime, map, scan } from 'rxjs/operators';

fromEvent(document, 'click')
  .pipe(
    throttleTime(1000),
    map((event: MouseEvent) => event.clientX),
    scan((count, clientX) => count + clientX, 0)
  )
  .subscribe(count => console.log(count));

RxJS vs Promise

Observable和Promise的不同点

  1. 可观察对象是声明式的,在被订阅之前,它不会开始执行。承诺是在创建时就立即执行的。这让可观察对象可用于定义那些应该按需执行的菜谱。
class AppComponent {
    newPromise() {
      const p = new Promise(resolve => {
        console.log('initial a promise'); // 立即触发
      });
    }
    newObservable() {
      const o = new Observable(subscriber => {
        console.log('initial a newObservable'); // 不触发
      });
    }
}
  1. 串联 跟第一条类似,只有当调用subscribe方法时,才会执行所有管道函数
class AppComponent {
newPromise() {
    const p = new Promise(resolve => {
      resolve(['a', 'b', 'c']);
    }).then(res => {
      console.log('第一个then');
      return res;
    }).then(res => {
      console.log('第2个then');
      return res;
    });
  }
  //======================================================
  newObservable() {
    const o = new Observable(subscriber => {
      console.log('initial a newObservable');
      subscriber.next(['a', 'b', 'c']);
    }).pipe(
      map(res => {
        console.log('第一个map');
        return res;
      }),
      map(res => {
        console.log('第2个map');
        return res;
      })
    );  // 不订阅,pipe中的所有函数都不会触发
  }
}
  1. Observable可以手动取消
const sub = interval(1000).subscribe(res => {
  console.log('interval', res);
});
class App {
  cancelObservable() {
      sub.unsubscribe();
  }
}

而Promise被解析时自动完成。

Observable 可(被)观察对象

Observable负责从数据源中推送数据,类似Promise

import { Observable } from 'rxjs';

const observable = new Observable(subscriber => {
  // 推送三个数据
  subscriber.next(1);
  subscriber.next(2);
  subscriber.next(3);
});

console.log('before subscribe');
observable.subscribe(x => {
  console.log('获得 value ' + x);
});
console.log('subscribe');

lazy computations

之前与Promise对比时讲过,只要不订阅(调用subscribe)Observable,Observable的回调函数就不会执行

import { Observable } from 'rxjs';

const foo = new Observable(subscriber => {
  console.log('Hello');
  subscriber.next(42);
});

foo.subscribe(x => {
  console.log(x);
});

Observable可同步,也可异步 推送值

import { Observable } from 'rxjs';

const foo = new Observable(subscriber => {
  console.log('Hello');
  subscriber.next(42);
});

console.log('before');
foo.subscribe(x => {
  console.log(x);
});
console.log('after');

下面是异步:

import { Observable } from 'rxjs';

const foo = new Observable(subscriber => {
  console.log('Hello');
  subscriber.next(42);
  subscriber.next(100);
  subscriber.next(200);
  setTimeout(() => {
    subscriber.next(300); // happens asynchronously
  }, 1000);
});

console.log('before');
foo.subscribe(x => {
  console.log(x);
});
console.log('after');

output:

"before"
"Hello"
42
100
200
"after"
300

创建Observables

可用new Observable创建,但实际情况更多的是用of, from, interval等操作符创建

import { Observable } from 'rxjs';

const observable = new Observable(function subscribe(subscriber) {
  const id = setInterval(() => {
    // 每秒推送一个 hi
    subscriber.next('hi')
  }, 1000);
});

Observer 观察

Observer用户获取到Observable推送的值,Observers是一系列回调函数,也就是Observable.subscribe的回调函数

Observable.subscribe(x => console.log(x));

之前已经多次订阅过Observables,但都不是完整写法
Observable.subscribe方法有三个回调函数,上面写的只是其中最常用但一个

  • "Next": 接收Observable推送过来但值
  • "Error": 接收错误对象
  • "Complete": 推送结束时触发(即使出现error),不会收到任何值

手动结束推送

import { Observable } from 'rxjs';

const observable = new Observable(function subscribe(subscriber) {
  subscriber.next(1);
  subscriber.next(2);
  subscriber.next(3);
  // 不调用complete方法,下面的observable.subscribe就不会输出complete
  subscriber.complete();
  subscriber.next(4); // 结束后再推送值,observable.subscribe也接收不到了
});

observable.subscribe(value => {
  console.log('value', value);
}, error => {
  console.error('error', error);
}, () => {
  console.log('complete');
});

发生错误

const observable = new Observable(function subscribe(subscriber) {
  subscriber.next(1);
  subscriber.next(2);
  subscriber.error(new Error('出错了'));
});
observable.subscribe(value => {
  console.log('value', value);
}, error => {
  console.error('error err', error);
}, () => {
  console.log('complete');
});

可以用try/catch捕获错误

import { Observable } from 'rxjs';

const observable = new Observable(function subscribe(subscriber) {
  try {
    subscriber.next(1);
    subscriber.next(2);
    subscriber.next(3);
    subscriber.complete();
  } catch (err) {
    subscriber.error(err); // delivers an error if it caught one
  }
});

Observable.subscribe的完整写法

observable.subscribe(value => {
  console.log('value', value);
}, error => {
  console.error('error err', error);
}, () => {
  console.log('complete');
});
observable.subscribe({
  next(value) {
    console.log('value', value);
  },
  error(error) {
    console.error('error', error);
  },
  complete() {
    console.log('complete');
  }
});

subscription 订阅

  • 取消订阅

observable.subscribe返回一个Subscription对象

const subscription = observable.subscribe(x => console.log(x));

调用subscription.unsubscribe()即可取消订阅

const observable = interval(1000);
const subscription = observable.subscribe(x => console.log(x));
setTimeout(() => {
  subscription.unsubscribe();
}, 5000);

Operator

什么是Operator

之前讲过什么是函数试编程,那么Operator就是一个个的函数,所以这些函数都具有函数式的特点:

  • 都有返回值
  • 不会对元数据产生副作用

即每个操作符(函数), 在拿到原Observable对象后,经过处理,都是返回一个新的Observable 比如下面这个自定义map函数:

const people = of('Jerry', 'Anna');
function map(source, callback) {
  return new Observable(observer => {
    return source.subscribe(
      value => {
        try{
          observer.next(callback(value));
        } catch (e) {
          observer.error(e);
        }
      },
      (err) => { observer.error(err); },
      () => { observer.complete(); }
    );
  });
}

const helloPeople = map(people, (item) => item + ' Hello~');
helloPeople.subscribe(res => {
  console.log('res', res);
});

这个map函数符合函数式的特点,在rxjs中,可称它为一个操作符(operator)

Operator分类

  • 管道操作符:filter, take...
  • 创建类操作符:of,from...

Marble diagrams

异步往往是复杂的,尤其是多个异步结合在一起的逻辑,用文字难以表达 所以出现了一种叫Marble diagrams的图形表示法,协助理解各種 operators! 参考:

创建类操作符

参考官网https://rxjs-cn.github.io/learn-rxjs-operators/

of

按顺序发出任意类型和数量的值

import { of } from 'rxjs';
const source = of(1, 2, 3, 4, 5);
// 输出: 1,2,3,4,5
source.subscribe(val => console.log(val));

//========================================================
import { of } from 'rxjs';
const source = of({ name: 'Brian' }, [1, 2, 3], function hello() {
  return 'Hello';
});

// 输出: {name: 'Brian}, [1,2,3], function hello() { return 'Hello' }
const subscribe = source.subscribe(val => console.log(val));

from

将数组、promise 或迭代器转换成 observable 数组

import { from } from 'rxjs';
const arraySource = from([1, 2, 3, 4, 5]);
// 输出: 1,2,3,4,5

const subscribe = arraySource.subscribe(val => console.log(val));

转Promise

import { from } from 'rxjs';

const promiseSource = from(new Promise(resolve => resolve('Hello World!')));

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

转Map对象

import { from } from 'rxjs';
const map = new Map([
  [1, 'hi']
]);
map.set(2, 'Bye');
map.set(3, 'rxjs');
const mapSource = from(map);

// 输出: [1, 'Hi'], [2, 'Bye'], [3, 'rxjs']
const subscribe = mapSource.subscribe(val => console.log(val));

转字符串

import { from } from 'rxjs';

// 将字符串作为字符序列发出
const source = from('Hello World');
// 输出: 'H','e','l','l','o',' ','W','o','r','l','d'
source.subscribe(val => console.log(val));

empty

不带任何数据的的Observable, 会立即执行complete回调

import { empty } from 'rxjs';
const result = empty();
result.subscribe(res => console.log(res), error => {}, () => console.log('ok'));

fromEvent

将事件转换成 observable

import { fromEvent } from 'rxjs';
import { map } from 'rxjs/operators';

// 创建发出点击事件的 observable
const source = fromEvent(document, 'click');
const example = source.pipe(map(event => `Event time: ${event.timeStamp}`));
example.subscribe(val => console.log(val));

interval

import { interval } from 'rxjs';

// 每1秒发出数字序列中的值
const source = interval(1000);
// 数字: 0,1,2,3,4,5....
const subscribe = source.subscribe(val => console.log(val));

timer

import { timer } from 'rxjs';
// 1秒后发出0,然后结束
const source = timer(1000);
// 输出: 0
const subscribe = source.subscribe(val => console.log(val));


import { timer } from 'rxjs';
// 1秒后发出一个值,然后每两秒继续发出值
const source = timer(1000, 2000);
// 输出: 0,1,2,3,4,5......
const subscribe = source.subscribe(val => console.log(val));

range

连续发出一定范围的数字

import { range } from 'rxjs';
// 从2开始,发出5个数字
const numbers = range(2, 5);
// 2 3 4 5 6
numbers.subscribe(x => console.log(x));


import { range } from 'rxjs';
// 如果只填写一个参数,如下:从0开始,发出4个数字
const numbers = range(4);
// 0 1 2 3
numbers.subscribe(x => console.log(x));

iif

在被订阅时,根据条件决定, 哪个Observable将被订阅

import { iif } from 'rxjs';
const random = Math.random();
console.log('random', random);

const firstOrSecond = iif(
  () => random > 0.5,
  of('first'),
  of('second'),
);

firstOrSecond.subscribe(res => {
  console.log('res', res);
});

throwError

创建一个立即触发error回调的Observable

import { throwError } from 'rxjs';
const err$ = throwError(new Error('fail'));
err$.subscribe(res => {
  console.log('res', res);
}, error => {
  console.error(error);
}, () => console.log('complete'));

创建+合并类操作符

combineLatest

合并多个Observable,并且返回每个Observable的最新值, 必须要每个Observable都有发出值,combineLatest才能被订阅

import { combineLatest, timer } from 'rxjs';

const firstTimer = timer(0, 1000);
const secondTimer = timer(500, 1000);
const combinedTimers = combineLatest(firstTimer, secondTimer);
combinedTimers.subscribe(value => console.log(value));
//[0,0],[1,0],[1,1],[2,1],[2,2]...

concat

类似数组的concat,将每个Observable拼接起来,按顺序发出值

import {interval, range, concat} from 'rxjs';
import {take} from 'rxjs/operators';

const timer$ = interval(1000).pipe(take(4));
const sequence$ = range(1, 10);
const result = concat(timer$, sequence$);
result.subscribe(x => console.log(x));
//1,2,3,4,1,2,3,4,5,6,7,8,9,10

forkJoin

类似Promise.all, 等每个Observables都完成后,合并它们发出的最后一个值

import { forkJoin, of, timer } from 'rxjs';

const observable = forkJoin([
  of(1, 2, 3, 4),
  Promise.resolve(8),
  timer(4000),
]);
observable.subscribe({
 next: value => console.log(value),
 complete: () => console.log('This is how it ends!'),
});
//[4,8,0]  This is how it ends!

merge

将多个Observable合并,与concat的行为不同,merge是把值按发射的顺序,逐个进行融合

import { merge, fromEvent, interval } from 'rxjs';

const clicks$ = fromEvent(document, 'click');
const timer$ = interval(1000);
const clicksOrTimer = merge(clicks$, timer$);
clicksOrTimer.subscribe(x => console.log(x));
//0,1,2,3,4,5,7每次点击把click事件插入中间

设置最多合并几个Observable

const timer1 = interval(1000).pipe(take(10), mapTo('a'));
const timer2 = interval(2000).pipe(take(6), mapTo('b'));
const timer3 = interval(500).pipe(take(10), mapTo('c'));

// 最后一个参数设为2,表示不管合并了多少个流,最多也只能merge其中的两个(与参数顺序无关)
const merged = merge(timer1, timer2, timer3, 2);
merged.subscribe(x => console.log(x));
//把a,b合并完了才开始合并c

partition

将一个Observable按条件分成两个

const observableValues = of(1, 2, 3, 4, 5, 6);
const [evens$, odds$] = partition(observableValues, (value, index) => value % 2 === 0);
odds$.subscribe(x => console.log('odds', x));
evens$.subscribe(x => console.log('evens', x));
//1,3,5,2,4,6

race

类似Promise.race,在多个Observable中,推送最快发出值的那一个

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

const obs1 = interval(1000).pipe(mapTo('fast one'));
const obs2 = interval(3000).pipe(mapTo('medium one'));
const obs3 = interval(5000).pipe(mapTo('slow one'));

race(obs3, obs1, obs2).subscribe(winner => console.log(winner));
//fast one,fast one,fast one...

zip

合并多个Observable,会严格按照发射值的位置(索引),合并每个流发出的值 也就是将每个Observable的第一个值合并,第二个值合并 ··· 第n个值合并

import { zip, of } from 'rxjs';

const age$ = of<number>(27, 25, 29);
const name$ = of<string>('Foo', 'Bar', 'Beer');
const isDev$ = of<boolean>(true, true, false);
zip(age$, name$, isDev$).subscribe(x => console.log(x));
//[27,'Foo',true],[25, 'Bar',true],[29,'Beer',false]

合并类操作符

高阶的Observable,一个Observable中还嵌套着其它Observable

combineAll

高阶转低阶 等到Observable完成后,对收集到的observables使用combineLatest

import { fromEvent, interval } from 'rxjs';
import { map, combineAll, take } from 'rxjs/operators';

const clicks = fromEvent(document, 'click');

/*
  每次点击,都会映射成interval发出的三个值
  点击三次higherOrder才算完成
  然后会把每次点击发出的值使用combineLatest策略合并每次点击的最新值
*/
const higherOrder = clicks.pipe(
  map(ev =>
    interval(2000).pipe(take(3))
  ),
  take(3)
);
const result = higherOrder.pipe(combineAll());

result.subscribe(x => console.log(x));
//
//higherOrder.subscribe(x => x.subscribe(res => console.log(res));

concatAll

高阶转低阶,按顺序订阅每一个Observable

import { fromEvent, interval } from 'rxjs';
import { map, take, concatAll } from 'rxjs/operators';

const clicks = fromEvent(document, 'click');
const higherOrder = clicks.pipe(
  map(ev => interval(1000).pipe(take(4))),
);
const firstOrder = higherOrder.pipe(concatAll());
firstOrder.subscribe(x => console.log(x));

mergeAll

高阶转低阶,发射内容Observable的所有值

import { fromEvent, interval } from 'rxjs';
import { map, mergeAll } from 'rxjs/operators';

const clicks = fromEvent(document, 'click');
const higherOrder = clicks.pipe(map((ev) => interval(1000)));
const firstOrder = higherOrder.pipe(mergeAll());
firstOrder.subscribe(x => console.log(x));

startWith和endWith

  • startWith: 在Observable发射值之前,先发射一个指定的值
  • endWith: 在Observable完成后,再发射一个指定的值
import { of } from 'rxjs';
import { startWith, endWith } from 'rxjs/operators';
of('from source')
  .pipe(startWith('first', 'second'), endWith('end'))
  .subscribe(x => console.log(x));

withLatestFrom

类似combinelatest,不过它只保证第二个流的值是最新的

import { fromEvent, interval } from 'rxjs';
import { withLatestFrom, pluck } from 'rxjs/operators';

const clicks = fromEvent(document, 'click').pipe(pluck('clientX'));
const interval$ = interval(1000);
// const result = clicks.pipe(withLatestFrom(interval$));
const result = interval$.pipe(withLatestFrom(clicks));
result.subscribe(x => console.log(x));

转换类操作符

buffer

用数组收集一个流发出的值,直到另一个流发出值,就把当前已收集到的值发出并释放

import { fromEvent, interval } from 'rxjs';
import { buffer } from 'rxjs/operators';

const clicks = fromEvent(document, 'click');
const intervalEvents = interval(1000);
const buffered = intervalEvents.pipe(buffer(clicks));
buffered.subscribe(x => console.log(x));

bufferCount

用数组收集一个流发出的值, 直到达到给定的最大值(bufferSize), 如果指定了startBufferEvery,意味着每次新一轮的收集会缓存前一次收集的startBufferEvery个值

const clicks = fromEvent(document, 'click');

// 只要收集到3个值,就将值推送出去
const buffered = clicks.pipe(bufferCount(3));
buffered.subscribe(x => console.log(x));
const clicks = fromEvent(document, 'click').pipe(pluck('clientX'));
// 只要收集到3个值,就将值推送出去,并且下一轮收集会从上一轮指定索引的位置开始复用
const buffered = clicks.pipe(bufferCount(3, 1));
buffered.subscribe(x => console.log(x));

bufferTime

每隔指定的时间发出收集到的值

import { fromEvent } from 'rxjs';
import { bufferTime } from 'rxjs/operators';

const clicks = fromEvent(document, 'click');
const buffered = clicks.pipe(bufferTime(1000));
buffered.subscribe(x => console.log(x));

bufferToggle

在指定的缓冲时间段内收集所有的值,到时间后关闭该时间段并发出所有的值

import { interval } from 'rxjs';
import { bufferToggle } from 'rxjs/operators';

const sourceInterval = interval(1000);
const startInterval = interval(5000);
const closingInterval = val => {
  console.log(`${val} 开始缓冲! 3秒后关闭`);
  return interval(3000);
};
// 每5秒会开启一个新的缓冲区以收集发出的值,3秒后发出缓冲的值,并关闭当前缓冲区
const bufferToggleInterval = sourceInterval.pipe(
  bufferToggle(
    startInterval,
    closingInterval
  )
);
const subscribe = bufferToggleInterval.subscribe(val =>
  console.log('Emitted Buffer:', val)
);

bufferWhen

收集值,直到指定的Observable发出值

const oneSecondInterval = interval(1000);
const clicks = fromEvent(document, 'click');
const bufferWhenExample = oneSecondInterval.pipe(bufferWhen(() => clicks));
const subscribe = bufferWhenExample.subscribe(val =>
  console.log('Emitted Buffer: ', val)
);

concatMap

将源Observable发出的每个值,按顺序映射成一个新的Observable

const source = of(10, 100);
const example = source.pipe(concatMap(val => of(val * 2)));
const subscribe = example.subscribe(val =>
  console.log('Example w/ Promise:', val)
);

concatMapTo

将源Observable发出的每个值,按顺序映射成一个固定值(不限于Observable)

const source = of(10, 100);
const example = source.pipe(concatMapTo(of('abc')));
const subscribe = example.subscribe(val =>
  console.log('Example w/ Promise:', val)
);

exhaust

当一个Observable为完成前,忽略所有其它的Observable

import { fromEvent, interval } from 'rxjs';
import { exhaust, map, take } from 'rxjs/operators';

const clicks = fromEvent(document, 'click');
const higherOrder = clicks.pipe(
  map((ev) => interval(1000).pipe(take(5))),
);
const result = higherOrder.pipe(exhaust());
result.subscribe(x => console.log(x));

exhaustMap

可以理解为map + exhaust的结合,比如上个例子可简写为:

const clicks = fromEvent(document, 'click');
const higherOrder = clicks.pipe(
  exhaustMap(ev => interval(1000).pipe(take(5)))
);
higherOrder.subscribe(x => console.log(x));

mergeMap

把源Observable发出的每个值,经过处理,映射成新的Observable

const source = of('Hello');
const myPromise = val =>
new Promise(resolve => resolve(`${val} World From Promise!`));
const example = source.pipe(mergeMap(val => myPromise(val)));
const subscribe = example.subscribe(val => console.log(val));

mergeMapTo

将源Observable发出的每个值,映射成新的值(不限于Observable)

const clicks = fromEvent(document, 'click');
const result = clicks.pipe(mergeMapTo(interval(1000)));
result.subscribe(x => console.log(x));

scan

在源Observable上应用累加器函数,每次累加后返回一个Observable

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

mergeScan

类似scan, 在源Observable上应用累加器函数,每次累加后返回一个Observable 上个例子重写:

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

reduce

类似scan,只不过会等源 observable 完成时将结果发出。

const source = of(1, 2, 3, 4);
const example = source.pipe(reduce((acc, val) => acc + val));
const subscribe = example.subscribe(val => console.log('Sum:', val));

pairwise

将发出的值两两组合起来

const pairs = interval(1000).pipe(pairwise());
pairs.subscribe(x => console.log(x));

groupBy

将源Observable按条件发组发出

of(
  {id: 1, name: 'JavaScript'},
  {id: 2, name: 'Parcel'},
  {id: 2, name: 'webpack'},
  {id: 1, name: 'TypeScript'},
  {id: 3, name: 'TSLint'}
).pipe(
  groupBy(p => p.id),
  mergeMap(group => group.pipe(toArray()))
).subscribe(p => console.log(p));

pluck

const clicks = fromEvent(document, 'click');
const tagNames = clicks.pipe(pluck('target', 'tagName'));
tagNames.subscribe(x => console.log(x));

switchMap

把源Observable发出的每个值,经过处理,映射成新的Observable,但每次都会取消上一次的Observable,不管上次Observable是否完成

const clicks = fromEvent(document, 'click');
const result = clicks.pipe(switchMap((ev) => interval(1000)));
result.subscribe(x => console.log(x));

switchMapTo

把源Observable发出的每个值,经过处理,映射成固定的值(不限于Observable) 上例重写:

const clicks = fromEvent(document, 'click');
const result = clicks.pipe(switchMapTo(interval(1000)));
result.subscribe(x => console.log(x));

map

类似数组map,对源observable的每个值进行映射

import { from } from 'rxjs';
import { map } from 'rxjs/operators';

const source = from([1, 2, 3, 4, 5]);
const example = source.pipe(map(val => val + 10));
const subscribe = example.subscribe(val => console.log(val));

mapTo

类似数组map,对源observable的每个值映射成一个指定的值

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

// 每2秒发出值
const source = interval(2000);
// 将所有发出值映射成同一个值
const example = source.pipe(mapTo('HELLO WORLD!'));
// 输出: 'HELLO WORLD!'...'HELLO WORLD!'...'HELLO WORLD!'...
const subscribe = example.subscribe(val => console.log(val));

过滤类操作符

audit

在定时器发出值之前,会忽略所有源Observable发出的值,直到定时器发出值,会推送源Observable最近发出的一次值

const clicks = fromEvent(document, 'click').pipe(pluck('clientX'));
const result = clicks.pipe(audit(ev => interval(2000)));
result.subscribe(x => console.log(x));

auditTime

类似audit,上例可重写为

const clicks = fromEvent(document, 'click').pipe(pluck('clientX'));
const result = clicks.pipe(auditTime(2000));
result.subscribe(x => console.log(x));

debounce

当源Observable的发射间隔大于指定timer时,才会发出最近的一次值
与audit的区别是:

  • audit是否发出值取决于timer是否发出值
  • debounce是否发出值取决源Observable的发射间隔是否大于给定的timer
const clicks = fromEvent(document, 'click');
const result = clicks.pipe(debounce(() => interval(1000)));
result.subscribe(x => console.log(x));s

debounceTime

类似debounce,上例可重写为

const clicks = fromEvent(document, 'click');
const result = clicks.pipe(debounceTime(1000));
result.subscribe(x => console.log(x));

distinct

依次发出源Observable的值,只是每次只发出与之前不同的值(或者之前从没出现过的值)

of(1, 1, 2, 2, 2, 1, 2, 3, 4, 3, 2, 1).pipe(distinct()).subscribe(x => console.log(x)); // 1, 2, 3, 4

还可以指定过滤函数

interface Person {
   age: number,
   name: string
}
of<Person>(
  { age: 4, name: 'Foo'},
  { age: 7, name: 'Bar'},
  { age: 5, name: 'Foo'},
).pipe(
  distinct((p: Person) => p.name),
)
.subscribe(x => console.log(x));

distinctUntilChanged

当源Observable发出了与上一次不同的值时,才把当前值推送出去

of(1, 1, 2, 2, 2, 1, 1, 2, 3, 3, 4).pipe(distinctUntilChanged())
  .subscribe(x => console.log(x)); // 1, 2, 1, 2, 3, 4

还可以指定过滤函数

interface Person {
   age: number,
   name: string
}
 
of<Person>(
    { age: 4, name: 'Foo'},
    { age: 7, name: 'Bar'},
    { age: 5, name: 'Foo'},
    { age: 6, name: 'Foo'},
  ).pipe(
    distinctUntilChanged((p: Person, q: Person) => p.name === q.name),
  )
  .subscribe(x => console.log(x));

distinctUntilKeyChanged

当源Observable发出的值,它的key与上一次值的key不同时,才把当前值推送出去

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

 interface Person {
    age: number,
    name: string
 }

of<Person>(
    { age: 4, name: 'Foo'},
    { age: 7, name: 'Bar'},
    { age: 5, name: 'Foo'},
    { age: 6, name: 'Foo'},
  ).pipe(
    distinctUntilKeyChanged('name'),
  )
  .subscribe(x => console.log(x));

更精细的匹配

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

interface Person {
    age: number,
    name: string
 }

of<Person>(
    { age: 4, name: 'Foo1'},
    { age: 7, name: 'Bar'},
    { age: 5, name: 'Foo2'},
    { age: 6, name: 'Foo3'},
  ).pipe(
    distinctUntilKeyChanged('name', (x: string, y: string) => x.substring(0, 3) === y.substring(0, 3)),
  )
  .subscribe(x => console.log(x));

elementAt

发出指定索引的那个值

const clicks = fromEvent(document, 'click');
const result = clicks.pipe(elementAt(2));
result.subscribe(x => console.log(x));

ignoreElements

忽略源Observable发出的所有值,直接complete

of('you', 'talking', 'to', 'me').pipe(ignoreElements())
    .subscribe(
      word => console.log(word),
      err => console.log('error:', err),
      () => console.log('the end')
    );

filter

类似数组的filter

import { fromEvent } from 'rxjs';
import { filter } from 'rxjs/operators';

const clicks = fromEvent(document, 'click');
const clicksOnDivs = clicks.pipe(filter(ev => ev.target.tagName === 'DIV'));
clicksOnDivs.subscribe(x => console.log(x));

first

只取第一个发出的值

import { fromEvent } from 'rxjs';
import { first } from 'rxjs/operators';

const clicks = fromEvent(document, 'click');
const result = clicks.pipe(first());
result.subscribe(x => console.log(x));

也可以指定第一个值符合的条件

import { fromEvent } from 'rxjs';
import { first } from 'rxjs/operators';

const clicks = fromEvent(document, 'click');
const result = clicks.pipe(first(ev => ev.target.tagName === 'DIV'));
result.subscribe(x => console.log(x));

last

只取最后一个值

of('you', 'talking', 'to', 'me').pipe(last()).subscribe(res => console.log(res));

sample

忽略源Observable发出的值,直到另一个Observable发出值,才推送源Observable最近发出的值

const seconds = interval(1000);
const clicks = fromEvent(document, 'click');
const result = seconds.pipe(sample(clicks));
result.subscribe(x => console.log(x));

sampleTime

每隔指定的时间发出最近的一个值

// 每隔3秒发出最近的一个值
const clicks = fromEvent(document, 'click');
const result = clicks.pipe(sampleTime(3000));
result.subscribe(x => console.log(x));

single

类似first,发出第一个值,但是如果源Observable有多个值,就会直接进入error

const numbers = range(1, 5).pipe(single());

// error
numbers.subscribe(x => console.log('never get called'), e => console.log('error'));
const numbers = range(1).pipe(single());

// get result 0
numbers.subscribe(x => console.log('get result', x), e => console.log('error'));

也可以指定过滤函数, 筛选出的结果必须只有一个符合的值,否则也是直接进入error

const numbers = range(1, 5).pipe(single(item => item === 3));

// get result 3
numbers.subscribe(x => console.log('get result', x), e => console.log('error'));


const numbers2 = range(1, 5).pipe(single(item => item > 3));

// error
numbers2.subscribe(x => console.log('get result', x), e => console.log('error'));

skip

跳过前面n个值开发推送数据

const source = interval(1000);
const example = source.pipe(skip(5));
const subscribe = example.subscribe(val => console.log(val));

skipLast

忽略最后n个值

const many = range(1, 5);
const skipLastTwo = many.pipe(skipLast(2));
skipLastTwo.subscribe(x => console.log(x));

skipUntil

一直忽略源Observable发出的值,直到另一个Observable发出值为止

const intervalObservable = interval(1000);
const click = fromEvent(document, 'click');
 
const emitAfterClick = intervalObservable.pipe(skipUntil(click));
// clicked at 4.6s. output: 5...6...7...8........ or
// clicked at 7.3s. output: 8...9...10..11.......
const subscribe = emitAfterClick.subscribe(value => console.log(value));

skipWhile

忽略所有符合条件的值

const source = interval(1000);
const example = source.pipe(skipWhile(val => val < 5));
const subscribe = example.subscribe(val => console.log(val));

take

只取前n个值

const intervalCount = interval(1000);
const takeFive = intervalCount.pipe(take(5));
takeFive.subscribe(x => console.log(x));

takeLast

只取最后n个值

const many = range(1, 100);
const lastThree = many.pipe(takeLast(3));
lastThree.subscribe(x => console.log(x));

takeUntil

不断推送源Observable发出的值,直到另一个Observable发出值为止

const source = interval(1000);
const clicks = fromEvent(document, 'click');
const result = source.pipe(takeUntil(clicks));
result.subscribe(x => console.log(x));

takeWhile

只取符合条件的值

const source = range(1, 8);
const example = source.pipe(takeWhile(val => val <= 4));
const subscribe = example.subscribe(val => console.log(val));

throttle

忽略源Observable发出的值,直到另一个Observable发出值,才会把最近的一次值推送出去

const interval$ = interval(500);
const result = interval$.pipe(throttle(ev => interval(2000)));
result.subscribe(x => console.log(x));

配置项:

const defaultThrottleConfig: ThrottleConfig = {
  leading: true,  // 是否每次节流开始前调用
  trailing: false // 是否每次节流开始后调用
};

throttleTime

类似throttle,上面例子可重写为

const interval$ = interval(500);
  const result = interval$.pipe(throttleTime(2000));
  result.subscribe(x => console.log(x));

错误处理操作符

之前演示过,如果Observable内部发出错误,只会进入error回调

of(1, 2, 3, 4, 5).pipe(
  map(n => {
    if (n === 4) {
      throw new Error('four err!');
    }
    return n;
  })
).subscribe(
  x => console.log(x),
  error => console.error('err', error),
  () => console.log('complete')
);

可以通过一些操作符改变这行为:

catchError

  • 无视错误,返回一个新的Observable
of(1, 2, 3, 4, 5).pipe(
  map(n => {
    if (n === 4) {
      throw new Error('four err!');
    }
    return n;
  }),
  catchError(err => of('I', 'II', 'III', 'IV', 'V')),
).subscribe(
  x => console.log(x),
  error => console.error('err', error), // 不会走这里
  () => console.log('complete')
);
  • 重试错误
of(1, 2, 3, 4, 5).pipe(
  map(n => {
    if (n === 4) {
      throw new Error('four err!');
    }
    return n;
  }),
  catchError((err, caught) => caught),
  take(30),
).subscribe(x => console.log(x));
  • 抛出一个新的错误
of(1, 2, 3, 4, 5).pipe(
  map(n => {
    if (n === 4) {
      throw new Error('four err!');
    }
    return n;
  }),
  catchError(err => {
    throw new Error('error in source. Details: ' + err);
  }),
)
  .subscribe(
    x => console.log(x),
    err => console.error('err ', err),
    () => console.log('complete') // 不会走这里
  );

retry

发生错误后重试指定次数

const source = interval(1000);
  const example = source.pipe(
    mergeMap(val => {
      if (val > 5){
        return throwError('Error!');
      }
      return of(val);
    }),
    // 重试两次
    retry(2)
  );

  const subscribe = example.subscribe({
    next: val => console.log(val),
    error: val => console.error('err ', `${val}: Retried 2 times then quit!`)
  });

retryWhen

发生错误后 自定义重试策略,参数是个回调函数,返回Observable

const source = interval(1000);
const example = source.pipe(
  map(val => {
    if (val > 5) {
      // 错误将由 retryWhen 接收
      throw val;
    }
    return val;
  }),
  retryWhen(errors =>
    errors.pipe(
      // 输出错误信息
      tap(val => console.log(`Value ${val} 太大了!`)),
      // 3秒后重试
      delay(3000)
    )
  )
);
const subscribe = example.subscribe(
  val => console.log(val),
  error => console.error('err ', error)
);

工具类操作符

tap

类似console.log,多用于调试,会返回源Observable的值

import { fromEvent } from 'rxjs';
import { tap, map } from 'rxjs/operators';

const clicks = fromEvent(document, 'click');
const positions = clicks.pipe(
  tap(ev => console.log(ev)),
  map(ev => ev.clientX),
);
positions.subscribe(x => console.log(x));

delay

把源Observable推送的每个值,都延迟一定时间推送,可以指定timeout,也可以指定未来具体的某个Date

import { fromEvent } from 'rxjs';
import { delay } from 'rxjs/operators';

const clicks = fromEvent(document, 'click');
const delayedClicks = clicks.pipe(delay(1000)); // each click emitted after 1 second
delayedClicks.subscribe(x => console.log(x));

指定未来的某个Date

import { fromEvent } from 'rxjs';
import { delay } from 'rxjs/operators';

const clicks = fromEvent(document, 'click');
const date = new Date('March 15, 2050 12:00:00'); // in the future
const delayedClicks = clicks.pipe(delay(date)); // click emitted only after that date
delayedClicks.subscribe(x => console.log(x));

delayWhen

把源Observable推送的每个值,都延迟一定时间推送,跟delay不同的是,每次延迟的时间都可以不同

const clicks = fromEvent(document, 'click');
const delayedClicks = clicks.pipe(
  // 每次点击都延迟2秒,相当于 delay(2000)
  // delayWhen(event => interval(2000))
  
  // 每次点击延迟0—5秒
  delayWhen(event => interval(Math.random() * 5000)),
);
delayedClicks.subscribe(x => console.log(x));

timeInterval

将源Observable发出的每个值转成一个Object,包含当前值、与上一次发出值时,经过的时间

const seconds = interval(1000);
seconds.pipe(timeInterval())
  .subscribe(
    value => console.log(value),
    err => console.log(err),
  );

timestamp

将源Observable发出的每个值转成一个Object,包含当前值、当前时间戳

const clickWithTimestamp = fromEvent(document, 'click').pipe(timestamp());

// Emits data of type {value: MouseEvent, timestamp: number}
clickWithTimestamp.subscribe(data => {
  console.log(data);
});

timeout

在指定时间内,不发出值就报错

const seconds = interval(1000);
seconds.pipe(timeout(1100))
.subscribe(
    value => console.log(value), // Will emit numbers just as regular `interval` would.
    err => console.log(err),     // Will never be called.
);


seconds.pipe(timeout(900))
.subscribe(
  value => console.log(value), // Will never be called.
  err => console.log(err)
);

也可以指定未来的某个Date

import { interval } from 'rxjs';
import { timeout } from 'rxjs/operators';

const seconds = interval(1000);

seconds.pipe(
  timeout(new Date("December 17, 2020 03:24:00")),
)
.subscribe(
    value => console.log(value), // 2020-12-17 03:24:00 前不会报错
    err => console.log(err)      // 到了2020-12-17 03:24:00就会开始报错了
);

timeoutWith

在指定时间内,不发出值就推送另一个Observable

const seconds = interval(1000);
const minutes = interval(500);

// seconds太慢将会推送minutes流
seconds.pipe(timeoutWith(1100, minutes))
  .subscribe(value => console.log(value));

数字类操作符

count

计算源Observable推送的个数

import { fromEvent, interval } from 'rxjs';
import { count, takeUntil } from 'rxjs/operators';

const seconds = interval(1000);
const clicks = fromEvent(document, 'click');
const secondsBeforeClick = seconds.pipe(takeUntil(clicks));
const result = secondsBeforeClick.pipe(count());
result.subscribe(x => console.log(x));
import { range } from 'rxjs';
import { count } from 'rxjs/operators';
const numbers = range(1, 7);
const result = numbers.pipe(count(i => i % 2 === 1));
result.subscribe(x => console.log(x));

max

求源Observable发出的最大值

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

of(5, 4, 7, 2, 8).pipe(max())
.subscribe(x => console.log(x)); // -> 8

也可以自定义比较规则

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

interface Person {
  age: number,
  name: string
}
of<Person>(
  {age: 7, name: 'Foo'},
  {age: 5, name: 'Bar'},
  {age: 9, name: 'Beer'},
).pipe(
  // 这里如果 -1 和 1 的位置对换,则求出的是最小值
  max<Person>((a: Person, b: Person) => a.age < b.age ? -1 : 1),
)
.subscribe((x: Person) => console.log(x.name)); // -> 'Beer'

min

求源Observable发出的最小值

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

of(5, 4, 7, 2, 8).pipe(
  min(),
)
.subscribe(x => console.log(x)); // -> 2
import { of } from 'rxjs';
import { min } from 'rxjs/operators';

interface Person {
  age: number,
  name: string
}
of<Person>(
  {age: 7, name: 'Foo'},
  {age: 5, name: 'Bar'},
  {age: 9, name: 'Beer'},
).pipe(
  min<Person>( (a: Person, b: Person) => a.age < b.age ? -1 : 1),
)
.subscribe((x: Person) => console.log(x.name)); // -> 'Bar'

条件类操作符

defaultIfEmpty

当源Observable没有发出任何值时(空的源),则指定发出一个默认值

const empty$ = EMPTY;
const result = empty$.pipe(defaultIfEmpty('empty default val'));
result.subscribe(x => console.log(x));

isEmpty

判断是否是空的Observable

import { EMPTY } from 'rxjs';
import { isEmpty } from 'rxjs/operators';

const result = EMPTY.pipe(isEmpty());
result.subscribe(x => console.log(x));
// Results in:
// true

以下操作符类似数组同名方法

findIndex

import { fromEvent } from 'rxjs';
import { findIndex } from 'rxjs/operators';

const clicks = fromEvent(document, 'click');
const result = clicks.pipe(findIndex(ev => ev.target.tagName === 'DIV'));
result.subscribe(x => console.log(x));

find

import { fromEvent } from 'rxjs';
import { find } from 'rxjs/operators';

const clicks = fromEvent(document, 'click');
const result = clicks.pipe(find(ev => ev.target.tagName === 'DIV'));
result.subscribe(x => console.log(x));

every

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

 of(1, 2, 3, 4, 5, 6).pipe(
    every(x => x < 5),
)
.subscribe(x => console.log(x)); // -> false

Subject

单播和多播

之前我们看到的所有Observable都是单播的,即源头有值发出时,不管这个Observable被几个Observer订阅,我一次只会给一个Observer推送
多播:当源头有值发出时,这个值会同一时间发给所有的Observer,
简单来说,单播与多播的区别类似于,concat和merge的区别

单播:

const source$ = range(5);
source$.subscribe(value => console.log('A: ' + value));
source$.subscribe(value => console.log('B: ' + value));

多播:

const source$ = range(5);
const subject$ = new Subject();
subject$.subscribe(value => console.log('A: ' + value));
subject$.subscribe(value => console.log('B: ' + value));
source$.subscribe(subject$);

什么是subject?

Subject是一种特殊的Observable,而且是多播的
既然是Observable,当然可以正常的被subscribe,只不过每个observer都会存在一份list中(这也是多播的原因),一旦有值发出,每个observer都会同时收到值
Subject还是Observer,可以执行next(), error(), complete()的方法

下面示范创建一个Subject, 并且有两个Observer订阅它:

import { Subject } from 'rxjs';

const subject = new Subject<number>();

subject.subscribe({
  next: (v) => console.log(`observerA: ${v}`)
});
subject.subscribe({
  next: (v) => console.log(`observerB: ${v}`)
});

subject.next(1);
subject.next(2);

// Logs:
// observerA: 1
// observerB: 1
// observerA: 2
// observerB: 2

Subject既然也是个Observer,自然可以作为subscribe的参数传入:

import { Subject, from } from 'rxjs';

const observable = from([1, 2, 3]);
const subject = new Subject<number>();
subject.subscribe({
  next: (v) => console.log(`observerA: ${v}`)
});
subject.subscribe({
  next: (v) => console.log(`observerB: ${v}`)
});
observable.subscribe(subject); // You can subscribe providing a Subject

// Logs:
// observerA: 1
// observerB: 1
// observerA: 2
// observerB: 2
// observerA: 3
// observerB: 3

上面这个例子,通过Subject将单播的Observable转成了多播的, 这是其中一种方式,rxjs提供了一些多播类的操作符也可以将单播的Observable转成了多播的

Subject的三个子类

BehaviorSubject

BehaviorSubject可以储存最新发送的一个值,只要有新的Observer订阅,就立马推送当前的最新值

先来看一个Subject的例子:

const subject = new Subject();
const observerA = {
  next: value => console.log('A next: ' + value),
  error: error => console.log('A error: ' + error),
  complete: () => console.log('A complete!')
}

const observerB = {
  next: value => console.log('B next: ' + value),
  error: error => console.log('B error: ' + error),
  complete: () => console.log('B complete!')
}
subject.subscribe(observerA);
subject.next(1);
subject.next(2);
subject.next(3);

setTimeout(() => {
  // 在这里subject已不再next()推送值,所以在这也订阅不到东西
  subject.subscribe(observerB);
}, 3000);

想要定时器里的observerB拿到最新值,只需改用BehaviorSubject BehaviorSubject必须指定一个初始值:

const subject = new BehaviorSubject(0); // 初始值为 0,相当于next(0)

下面是官网的例子:

import { BehaviorSubject } from 'rxjs';
const subject = new BehaviorSubject(0); // 0 is the initial value

subject.subscribe({
  next: (v) => console.log(`observerA: ${v}`)
});

subject.next(1);
subject.next(2);

subject.subscribe({
  next: (v) => console.log(`observerB: ${v}`)
});

subject.next(3);

// Logs
// observerA: 0
// observerA: 1
// observerA: 2
// observerB: 2
// observerA: 3
// observerB: 3

ReplaySubject

ReplaySubject可以指定推送最近的多少个值给新的Observer,而BehaviorSubject只会推最近的一个值
官网的例子:

import { ReplaySubject } from 'rxjs';
const subject = new ReplaySubject(3); // buffer 3 values for new subscribers

subject.subscribe({
  next: (v) => console.log(`observerA: ${v}`)
});

subject.next(1);
subject.next(2);
subject.next(3);
subject.next(4);

subject.subscribe({
  next: (v) => console.log(`observerB: ${v}`)
});

subject.next(5);

还可以指定第二个参数, 设置缓存的有效期,单位ms

// 缓存3个值,并且只在5秒内有效,超过5秒新的Observer将不会订阅的任何值
const subject = new ReplaySubject(3, 5000 /* windowTime */);
subject.subscribe({
  next: (v) => console.log(`observerA: ${v}`)
});
range(5).subscribe(value => subject.next(value));

setTimeout(() => {
  subject.subscribe({
    next: (v) => console.log(`observerB: ${v}`)
  });
}, 6000);

AsyncSubject

只在Subject结束时,推送最后一个值

import { AsyncSubject } from 'rxjs';
const subject = new AsyncSubject();

subject.subscribe({
  next: (v) => console.log(`observerA: ${v}`)
});

subject.next(1);
subject.next(2);
subject.next(3);
subject.next(4);

subject.subscribe({
  next: (v) => console.log(`observerB: ${v}`)
});

subject.next(5);
subject.complete();
// Logs:
// observerA: 5
// observerB: 5

冷和热

概念

Cold Observable:一个Observable不管被多少个Observer订阅,总是从头开始推送,每个Observer相互独立
Hot Observable:每一个新的Observer会从源Observable发出的最新值开始接收 cold 和 hot 相当于多例和单例的区别

code:

const source = interval(1000).pipe(take(5));
source.subscribe(value => console.log('observable1: ' + value));
setTimeout(() => {
  source.subscribe(value => console.log('observable1: ' + value));
}, 2000);

hot:

const source = interval(1000).pipe(take(5), share());
source.subscribe(value => console.log('observable1: ' + value));
setTimeout(() => {
  source.subscribe(value => console.log('observable1: ' + value));
}, 2000);

多播操作符

multicast

之前演示过用Subject将单播Observable转成多播的,multicast做的也是同样的事情

const source = from([1, 2, 3]);
const subject = new Subject();
const multicasted = source.pipe(multicast(subject)) as ConnectableObservable<number>;

// 相当于 `subject.subscribe({...})`:
multicasted.subscribe({
  next: (v) => console.log(`observerA: ${v}`)
});
multicasted.subscribe({
  next: (v) => console.log(`observerB: ${v}`)
});

// 相当于, `source.subscribe(subject)`:
multicasted.connect();

multicast返回一个ConnectableObservable, 它的connect方法决定源Observable何时开始推送值 该方法返回Subscription,支持取消订阅

看个官网例子:

const source = interval(500);
const subject = new Subject();
const multicasted = source.pipe(multicast(subject)) as ConnectableObservable<number>;
let subscription1, subscription2, subscriptionConnect;

subscription1 = multicasted.subscribe({
  next: (v) => console.log(`observerA: ${v}`)
});

subscriptionConnect = multicasted.connect();

setTimeout(() => {
  subscription2 = multicasted.subscribe({
    next: (v) => console.log(`observerB: ${v}`)
  });
}, 1000);

setTimeout(() => {
  subscription1.unsubscribe();
}, 2000);

setTimeout(() => {
  subscription2.unsubscribe();

  // subscription2退订后,source已经没有订阅者了,要加上这句才是真正的退订
  subscriptionConnect.unsubscribe();
}, 7000);

refCount

如果觉的multicast必须调用connect方法才能推送值 还要multicasted.unsubscribe()才能真正结束推送 有些麻烦,
就可以用refCount refCount:当有Observer订阅源Observable时,自动调用connect, 当Observer全部unsubscribe后,即没有Observer了,自动调用connect().unsubscribe()退订

const source = interval(500);
const subject = new Subject();
const refCounted = source.pipe(multicast(subject), refCount());
let subscription1, subscription2;


// 一旦有了subscriber,就会自动调用connect()
console.log('observerA subscribed');
subscription1 = refCounted.subscribe({
  next: (v) => console.log(`observerA: ${v}`)
});

setTimeout(() => {
  // 1秒后再增加一个subscriber
  console.log('observerB subscribed');
  subscription2 = refCounted.subscribe({
    next: (v) => console.log(`observerB: ${v}`)
  });
}, 1000);

setTimeout(() => {
  // 2秒后退订observerA
  console.log('observerA unsubscribed');
  subscription1.unsubscribe();
}, 2000);


setTimeout(() => {
  // 5秒后退订observerB,此时已经没有observer了,共享的source将结束推送
  console.log('observerB unsubscribed');
  subscription2.unsubscribe();
}, 5000);

publish

multicast(new Subject)很常用,可用publish将其简化 上个例子可以做如下简化:

const subject = new Subject();
const refCounted = source.pipe(multicast(subject), refCount());

// 等价于
const refCounted = source.pipe(publish(), refCount());

与Subject类似,publish也有三个变种方法:

publishBehavior(0) => new BehaviorSubject(0)
publishReplay(2) => new ReplaySubject(2)
publishLast() => new AsyncSubject()

const source = range(1, 5);
const refCounted = source.pipe(publishBehavior(0), refCount());


// 一旦有了subscriber,就会自动调用connect()
console.log('observerA subscribed');
refCounted.subscribe({
  next: (v) => console.log(`observerA: ${v}`)
});

setTimeout(() => {
  // 1秒后再增加一个subscriber
  console.log('observerB subscribed');
  refCounted.subscribe({
    next: (v) => console.log(`observerB: ${v}`)
  });
}, 2000);

share

publish + refCount 的简写

const source = interval(500);
const refCounted = source.pipe(share());


// 一旦有了subscriber,就会自动调用connect()
console.log('observerA subscribed');
refCounted.subscribe({
  next: (v) => console.log(`observerA: ${v}`)
});

setTimeout(() => {
  // 1秒后再增加一个subscriber
  console.log('observerB subscribed');
  refCounted.subscribe({
    next: (v) => console.log(`observerB: ${v}`)
  });
}, 2000);

类似的还有: shareReplay:

const source = range(2, 8);
const refCounted = source.pipe(shareReplay(2));


// 一旦有了subscriber,就会自动调用connect()
console.log('observerA subscribed');
refCounted.subscribe({
  next: (v) => console.log(`observerA: ${v}`)
});

setTimeout(() => {
  // 1秒后再增加一个subscriber
  console.log('observerB subscribed');
  refCounted.subscribe({
    next: (v) => console.log(`observerB: ${v}`)
  });
}, 2000);

练习

写一个很常见的,输入框搜索请求后台数据,显示在页面上的例子

<div class="autocomplete">
  <input #input class="form-control" placeholder="search..." />
  <ul class="list-group mt-2">
    <li class="list-group-item" *ngFor="let item of list">{{ item }}</li>
  </ul>
</div>
@Injectable()
class WikiService {
  readonly url = 'https://zh.wikipedia.org/w/api.php?action=opensearch&format=json&limit=5&origin=*&search=';
  list(keyword) {
    return ajax.getJSON(this.url + keyword).pipe(map(res => res[1]));
  }
}

@Component({
  selector: 'app-example',
  templateUrl: './example.component.html',
  styleUrls: ['./example.component.scss'],
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
  providers: [WikiService]
})
export class ExampleComponent implements OnInit, AfterViewInit {
  list: string[] = [];
  @ViewChild('input', { static: true }) private inputEl: ElementRef;
  constructor(private wikiServe: WikiService, private cdr: ChangeDetectorRef) {}

  ngOnInit(): void {}

  ngAfterViewInit(): void {
    fromEvent(this.inputEl.nativeElement, 'input')
      .pipe(
        debounceTime(500),
        pluck('target', 'value'),
        distinctUntilChanged(),  // 注意顺序
        switchMap(value => this.wikiServe.list(value)),
        retry(3)
      )
      .subscribe(list => {
        console.log('list', list);
        this.list = list;
        this.cdr.markForCheck();
      });
  }
}

换百度的搜索接口

  1. 安装jsonp-good插件
  2. 百度接口(https://www.baidu.com/sugrec?pre=1&p=3&ie=utf-8&json=1&prod=pc&from=pc_web&sugsid=32757,1461,32880,32940,32971,32706,7517,32913&wd=n&req=2&csor=1&pwd=li&cb=jQuery110206797386932220344_1604233361406&_=1604233361413)
  3. 改逻辑
import {
  Component,
  OnInit,
  ChangeDetectionStrategy,
  Injectable,
  ViewChild,
  ElementRef,
  AfterViewInit,
  ChangeDetectorRef
} from '@angular/core';
import {from, fromEvent, Observable, of} from 'rxjs';
import {ajax, AjaxResponse} from 'rxjs/ajax';
import { debounceTime, distinctUntilChanged, map, pluck, retry, switchMap, timeout } from 'rxjs/operators';
import jsonpG from 'jsonp-good';
interface JsonpRes {
  q: string;
}

@Injectable()
class WikiService {
  readonly url = 'https://www.baidu.com/sugrec';
  list(wd): Observable<JsonpRes[]> {
    return from(jsonpG({
      url: this.url,
      params: {
        prod: 'pc',
        from: 'pc_web',
        wd
      },
      funcName: 'jQuery110203052522071732855_1604236886158',
    }).then((res: { g: JsonpRes[] }) => {
      return res.g;
    }));
  }
}


@Component({
  selector: 'app-test-rx',
  template: `
    <div class="autocomplete">
      <input #input class="form-control" placeholder="search..." />
      <ul class="list-group mt-2">
        <li class="list-group-item" *ngFor="let item of list">{{ item?.q }}</li>
      </ul>
    </div>
  `,
  styles: [],
  changeDetection: ChangeDetectionStrategy.OnPush,
  providers: [WikiService]
})
export class TestRxComponent implements OnInit, AfterViewInit {
  list: JsonpRes[] = [];
}
posted @ 2020-12-01 17:20  Daeeman  阅读(335)  评论(0)    收藏  举报