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),使用像
map、filter、concat、flatMap等这样的操作符来处理集合。 - Subject (主体): 相当于 EventEmitter,并且是将值或事件多路推送给多个 Observer 的唯一方式。
- Schedulers (调度器): 用来控制并发并且是中央集权的调度员,允许我们在发生计算时进行协调,例如
setTimeout或requestAnimationFrame或其他。
常见的异步行为
- 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的不同点
- 可观察对象是声明式的,在被订阅之前,它不会开始执行。承诺是在创建时就立即执行的。这让可观察对象可用于定义那些应该按需执行的菜谱。
class AppComponent {
newPromise() {
const p = new Promise(resolve => {
console.log('initial a promise'); // 立即触发
});
}
newObservable() {
const o = new Observable(subscriber => {
console.log('initial a newObservable'); // 不触发
});
}
}
- 串联 跟第一条类似,只有当调用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中的所有函数都不会触发
}
}
- 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();
});
}
}
换百度的搜索接口
- 安装jsonp-good插件
- 百度接口(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)
- 改逻辑
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[] = [];
}

浙公网安备 33010602011771号