angular2 学习笔记 ( rxjs 流 )
更新: 08-04-2023
看移步修订版: RxJS 系列 – 目录
更新: 2020-11-01
zip 和 combineLatest 的区别于用法
combineLatest([ a, a ])
当 a next 的时候会触发 2 次, 第一次拿到的 value, 一个是新值, 一个是旧值
因为 rxjs 会认为这 2 个流不同, 虽然是一起触发,但其实监听还是讲顺序的. 所以第一个触发的时候, 第 2 个 a 其实用的是缓存值.
那么用 zip 就不会有这样的情况, 因为会等全部都触发第 2 次了才 run callback.
更新: 2020-09-26
让 switchMap 先返回一些值
value.pipe(switchmap()) 当 value change 的时候就做一些 ajax, 后面的 subscribe 没办法第一时间知道你去 ajax 了
这种情况下比较好的做法是返回一个 subject, swithmap 里面就 next 这个 subject 就行了, 或者返回一个 merge(of(null), ajax()) 也是可以.
更新: 2020-05-12
rxjs 有很多操作, 如果遇到不够用的时候也可以自己写.
写法超级简单.
写一个方法, 接受 Observable 返回 new Observable 就可以了.
然后就是调用时的参数. 很多时候我们会传入其它 obs 作为监听
返回的 obs 我们要记得写 unsubscribe 方法,一旦被 unsubscribe 就去释放其它监听.
这样就很安全了.
比如 :
function skipAfter<T>(skip$: Observable<void>): (source: Observable<T>) => Observable<T> { return (source: Observable<T>) => { return new Observable(subscribe => { const subscription = new Subscription(); let skip = false; subscription.add( skip$.subscribe(() => { skip = true; }) ); subscription.add( source.subscribe({ complete: () => subscribe.complete(), error: error => subscribe.error(error), next: v => { if (skip) { skip = false; } else { subscribe.next(v); } } }) ) return () => { subscription.unsubscribe(); } }); } }
使用起来时这样
const mainSubject = new Subject<string>(); const skipSubject = new Subject<void>(); const s = mainSubject.pipe( skipAfter(skipSubject) ).subscribe(v => { console.log(v); }); mainSubject.next('1'); mainSubject.next('2'); skipSubject.next(); mainSubject.next('3'); mainSubject.next('4'); skipSubject.next(); skipSubject.next(); mainSubject.next('5'); mainSubject.next('6'); s.unsubscribe();
result :
更新: 2020-04-03
今天写了一个 bug,来梳理一下
subject, observalbe, multicast, connect, publishReplay, refCount, ShareReplay
obs 是一个方法, 你 sub 它就等于调用它.
const o = new Observable((observer => { let i = 0; const timer = setInterval(() => { console.log('interval run'); observer.next(i++); }, 1000); return () => { window.clearInterval(timer); }; }));
比如上面这个, o.sub 就会调用里面的函数, 创建一个 interval, 你 sub 多少次就有多少个 interval 被创建.
全部是独立的. 最后返回的是 unsubscribe 后触发的事情, 就是 clear interval.
如果我们不希望这样,而希望它像观察者那样,大家公用一个 interval, 然后后面来的 sub 也立马拿到当前的值, 那么我们需要 subject
它的做法就是只 sub 一次 obs, 然后通过 ReplaySubject.next 去广播给后边的人.
subject 不是函数,所以它不存在每次 sub 都会创建新的东西那样. 它只是把 sub 的方法存在 array 而已,观察者模式的实现.
const o1 = o.pipe( multicast(new ReplaySubject<number>(1)), ) as ConnectableObservable<number>; const subscrition = o1.connect(); o1.subscribe(v => console.log(v)); subscrition.unsubscribe();
当 connect 被调用, interval 开始
当 connect 返回的 subscrition 被退订后, interval 就结束了
然后其它人 sub 的其实是 multicast 里得 ReplaySubject.
const o1 = o.pipe( multicast(new ReplaySubject<number>(1)), refCount(), ) as ConnectableObservable<number>; o1.subscribe(v => console.log(v));
refCount 是用来替代手动 connect 的. 它的逻辑是, 当第一个人 sub 的时候去 connect
同时当所有人都退订后去 disconnect
今天写的就是这个 bug, 我的第一个订阅是 toPromise, 执行完后立马就退订了. 导致后续的人 sub 的时候又去发 ajax 了.
所以 refCount 也不是每次都好用的.
multicast 可以用 publishReplay 来替代
multicast(new ReplaySubject<number>(1))
可以改写为
publishReplay(1)
而 publishReplay + refCount 不可以用 shareReplay 来替代, 在原 source complete 和 error 的时候, share replay 会创建新的订阅, 而 publish 只是直接返回 complete 状态, 这点不同。
publishReplay(1),
refCount(),
不可以用 shareReplay 来替代.
shareReplay({ refCount: true, bufferSize: 1 })
refCount 默认是 false, true 就是 refCount 咯, false 的话它算一半的 refCount
它依然具有第一个 sub 它就 connect 但是没有因为 0 而 disconnect. 这点满特别的哦
鼓励大家读读下面这篇, 其实有些细节我没搞太清楚,但是如果你遇到 bug 不妨认真看一看它们之前的微差.
https://itnext.io/the-magic-of-rxjs-sharing-operators-and-their-differences-3a03d699d255
在说说我的情况要怎么处理.
在一个 component 里, 我的 html 需要 subscribe 这个流.
而在 init 的时候我也需要这个 source value.
但是在 init 的时候我只要拿一次就好了. 并不需要 watch.
所以我一开始的写法就是 await obs.sub.take(1).toPromise()
由于只是 await + take 1 所以等 ajax 回来后立马就退订了.
这就导致后续的 html | async 的时候又去发 ajax 了. refCount 的原则是至少要有一个订阅者在线.
那么解决方法有 2 个方向, 第一个就是我在 promise 之前写一个订阅. 指导 component destory. 这个订阅不需要做什么,只是确保在线就好了.
另一个方法是 refCount : false. 这样它就不会因为没有订阅者在线而退订。然后再 shareplay 之前放一个 take until 来退订.当 component destory 的时候就触发.
更新: 2020-03-30
takeUntil vs unsubscribe
更新 : 2020-03-28
const s = new BehaviorSubject('s1'); const s2 = new BehaviorSubject('s2'); const o = s.pipe( switchMap(v => { return timer(300).pipe( tap(v => console.log('dada'), shareReplay({ bufferSize: 1, refCount: true }) )); }), shareReplay({ bufferSize: 1, refCount: true }) // 要放哦 ); o.subscribe(v => console.log(v)); s2.next('z'); o.subscribe(v => console.log(v));
里面 share 没啥用,关键是外面要 share,不然 dada 依据跑.
更新: 2020-02-12
一旦 Subscription 被 unsubscribe 后, 千万不要在 add 了, 它是不会触发的
const s = new Subject(); const a = new Subscription(); a.unsubscribe(); a.add(s.subscribe(v => console.log('done'))); s.next('aaa'); // 不会触发
我经常会开一个 subscription 然后把所有 sub add 进去,等到 destroy 的时候一次毁掉. 无意间发现原来有上面这种情况。
更新: 2020-01-19
小心 share replay 泄漏
refer :
https://blog.strongbrew.io/share-replay-issue/
https://blog.jerry-hong.com/series/rxjs/thirty-days-RxJS-24/
先看看历史...
https://stackoverflow.com/questions/47793518/share-operator-that-doesnt-unsubscribe
https://medium.com/angular-in-depth/rxjs-whats-changed-with-sharereplay-65c098843e95
obs 一但被订阅, 方法就会被调用. 如果不希望每次都重新调用,我们可以把结果存起来.
这时候就需要 subject 的帮忙了.
new subject 让往后的人都订阅 subject, subject 和 obs 不同,它是观察者模式, 不像 obs 每次重新 run 方法.
接下来就是把一开始的 obs 连接上 subject, 下面这长很好解释了
source.connect() 会返回一个退订方法作为 disconnect 用.
通常有 2 种预期的使用效果
1. 当调用 multicast 后立马执行 obs 然后把 value 存入 replay subject, 一有新值就 update. 往后的人就可以马上获取到值了.
2. 当有人订阅后才执行 obs, 然后一直保持更新, 当订阅人数 0 时退订 obs, 直到下一次订阅才又开始.
如果是第一种情况那么就直接 connect 就行了.
第 2 种的做法是通过 refCount()
好了,说主题 shareReplay, 它采用的是第一种, 以前用的是第 2 种. 所以会有点混乱.
所以最好呢,是我们在调用的时候自己 set 一下, 确保它是你要的, 比如
shareReplay({ refCount: false })
refCount false 意味着 obs 的 subscribe 不会因为 subject 退订而退订. 它会一直等到 obs complete
这很容易产生泄露. 所以建议还是用 refCount 的好哦.
refCount 则又可能导致 last value 丢失, 这个也是要多多留意一下, 当然如果 obs 是源头这个就不会发生,如果源头是 subject 就有可能啦.
更新: 2019-12-12
这位朋友的 work around 很聪明丫, 利用了 timer + finalize
timer 会触发 complete 然后 subscribe 会先执行, 最后 finalize 更新 status 多一次, 就触发了 emit
更新: 2019-11-24
startWith 和 pairwise
s.pipe( startWith('c'), map(v => v + 1), tap(v => console.log(v)), // c1 startWith(null), pairwise(), tap(v => console.log(v)), // [null, c1] ).subscribe(([before, after]) => { // console.log(before, after); });
2 个点要留意
第一,pairwise 需要 2次值才会触发. 所以 startWith('c') 到 pairwise 就被吃掉了. subscribe 不会触发
所以需要 startWith(null) 来喂它一次.
第二, startWith 的次序. 最后一个 tap 的值是 [null, c1] 而不是 [c1, null]
startWith('c1'), startWith('c2'), tap(v => console.log(v)), // [c2, c1]
要记住哦。
更新: 2019-07-18
unsubscribe vs complete
在用 ng 的时候, 我们会纠结什么时候要调用 unsubscribe, 因为据说 angular 会帮我们处理...
其实最好是每一次都调用 unsubscribe. 比如下面这个例子, setimeout 代表 component destroy
当 destroy 时,即使我 subject.complete(), 我也无法阻止 tap 的触发. 所以还是 unsubscribe 妥当一些.
按逻辑讲, 当 component destroy 我们是取消我们对外部的监听, 意思是我们不再处理了, 而 complete 则是它不再发送了.
它不再发送, 和我不再处理是 2 个概念. 它不发, 但是我手上的工作还是得做完, 我不处理是我立马停掉手上工作.
const s = new Subject(); const o = s.asObservable(); const sub = o.pipe(delay(3000), tap(() => console.log('tap'))).subscribe(() => { console.log('done'); }); s.next('dada'); setTimeout(() => { s.complete(); // sub.unsubscribe(); }, 1000);
更新 : 2019-06-2
defer 用于延后一个 function 执行. 当 subscribe 后才被执行, 通常用于做 promise retry
比如我有一个 promise 方法
async function getValueAsync(ok: boolean): Promise<string> { return new Promise((resolve, reject) => { console.log('run'); setTimeout(() => { if (ok) { resolve('dada'); } else { reject('fail'); } }, 3000); }); }
from(getValueAsync(false)).pipe( retry(1) ).subscribe({ next: (value) => { console.log(value); }, error: (err) => { console.log('Error: ' + err); }, complete: () => { console.log('complete'); } });
如果我直接这样跑是不能 retry 的. refer : https://stackoverflow.com/questions/33072512/rx-frompromise-and-retry 这里有解释
用 defer
defer(() => getValueAsync(false)).pipe( retry(2) ).subscribe({ next: (value) => { console.log(value); }, error: (err) => { console.log('Error: ' + err); }, complete: () => { console.log('complete'); } });
这样就可以了
更新 : 2018-03-12
学 rxjs 最好的就是看官网的文档,解释的很清楚.
http://cn.rx.js.org/manual/overview.html#h39
https://rxjs-cn.github.io/learn-rxjs-operators/
function subscribe(observer) { var intervalID = setInterval(() => { observer.next('hi'); }, 1000); return function unsubscribe() { clearInterval(intervalID); }; } var unsubscribe = subscribe({next: (x) => console.log(x)}); var unsubscribe = subscribe({next: (x) => console.log(x)}); // 稍后: unsubscribe(); // 清理资源
上面这一段代码让我明白了几个重点
1. 惰性 (当 observable 被创建时,没有 subscribe 是不会开始运行的, 因为 observable 就像函数调用 )
2. 每一个 subscribe 是独立的
3. observable 和常用的 addEventListener 不同,它不会吧所有的 observer 保存在列表里头.
而 subject 则是道道地地的 addEventListener, 它会保存所有 observer 在列表里.
而把它们结合的方式就是 observable.subscribe(subject);
因为 subject 就是一个 observer 拥有 next 方法.
这就是 rxjs 其中 2 大核心, observable and subject
至于其它的 subject 还有一堆的 operator, create observable 等,都是基于这 2 个核心的扩展而已.
更新 : 2017-11-14
最近从新看了 30 天 rxjs, 这里补上一些笔记.
顺便提一下, ng 5.x 开始 rxjs 的写法换掉了
参考 : https://github.com/ReactiveX/rxjs/blob/master/doc/lettable-operators.md
import { combineLatest } from 'rxjs/observable/combineLatest'; import { catchError, take } from 'rxjs/operators'; combineLatest(pendingEmitters).pipe( take(1), catchError(() => { reject(); return ''; }) ).subscribe(() => { resolve(); });
不像 jquery 那样串连了.
1.concat
https://ithelp.ithome.com.tw/articles/10187520
concat 是把多个 observeable 合并起来, 其特色是只有前面一个 observeable complete 了后边的 observeable 才开始起作用.
concat(o1,o2,o3), o1 没有 complete 的话, o2 怎么叫都不会触发.
另一个说法就是 concat 先 subcribe o1, 然后等到 o1 completed 后再去 subscribe o2, 一直到完, 那它自己也就 completed 了.
concat 是可以调用的 import { concat } from 'rxjs/observable/concat';
2. merge
https://ithelp.ithome.com.tw/articles/10187520
它和 concat 都是用来合并的. 区别是它不需要等 complete, 任何一个 observable 触发都会有效果.
换句话说就是 merge 会直接 subcribe 所有的 obserable 不像 concat 那样会等 completed.
merge(o1,o2) o1 没有 complete, o2 叫一样有效.
3.concatAll
concatAll 属于 operators
o1.pipe(map(_ => o2),concatAll())
每一次 o1 叫,都会产生多一个 o2
比如 o1 叫了 3 次 , 那么就有 3 个 o2
concatAll 就是把这 3 个组合起来( 每一次 o1 叫, 都会 push 新的 o2 去这个 array ) 然后 concat(o2,o2,o2),后续的步骤就和 concat 一摸一样了. 所以它起到了打平和 concat 的作用.
4. mergeAll
mergeAll 和 concat 是同一个原理只是, 最后不是用 concat 而是用 merge(o2,o2,o2);
另外, mergeAll(2) 可以传入一个变量去控制允许并发的数量
比如你输入 2, 那么 a 叫了 3次, 你有 merge(o2,o2,o2), 第3个 o2 叫的时候本来是会有效果的,但是由于限制了 2, 那么只能等第一或第二个 o2 complete, 第 3 o2 叫才有效果了。确保同一时期只有 2 个.
note mergeAll(1) === concatAll()
5.switchAll
没有 switch 只有 switchAll
它和 concatAll, mergeAll 的区别是, o1 每一次叫, 永远只保留最新的 o2, 之前的 o2 统统丢掉.
concatAll, switchAll, mergeAll
https://ithelp.ithome.com.tw/articles/10188325
这 3 个都是用来打平 obs
concatAll 上面讲了重点是会等上一个 complete 才去下一个
switchAll 则是一旦有新的一个来,旧的就忽略掉.
mergeAll 则是并发处理.
6 concatMap, switchMap, mergeMap
https://ithelp.ithome.com.tw/articles/10188387
就是 map() + switchAll(), map() + concatAll(), map() + mergeAll() 的缩写而已.
还有个好处是它可以传入一个方法 (a,b,c,d) => next, 可以获取到 o1,o2 的值然后返回下一个值.
6.5 exhaustMap
这个和 concatMap 很像. 唯一的区别是, concat 等待第一个 complete 了以后会去 subscribe 下一个(第二个)
而 exhasust 呢, 它会去 subscribe 下下下下一个 (最后一个),
7. combineLatest
https://ithelp.ithome.com.tw/articles/10187638
它也是用来做合拼处理的,
它需要等 "每一个" observable 至少有一个开始值之后才开始工作. 这和 merge 不同, merge 不需要等
它每一次触发都可以获取到所有 observable 当前的值, 这和 merge 不同, merge 每一次触发只能获取一个 observable 的值.
8.withLatestFrom
https://ithelp.ithome.com.tw/articles/10187638
它和 combineLatest 一样,唯一的区别是, 它只有 main observable next 值时才会触发 callback , 其它 observable next 只是记入值而已.
2019-06-14 补上一个例子, form value update 但是只有 button click 的时候才 emit
this.formGroup = this.formBuilder.group({ date: [null, Validators.required] }); const f = this.formBuilder.group({ name: [''], age: [11] }); const button = new Subject(); button.pipe(withLatestFrom(f.valueChanges)).subscribe(v => { console.log(v); }); f.setValue({ name: 'dada', age: 15 }); f.setValue({ name: 'dada', age: 18 }); button.next('dada'); button.next('dada2');
9. zip
https://ithelp.ithome.com.tw/articles/10187638
它也是合并.
它的关键是顺位, 比如 2 个 observables, 2 个都 next 1次 的时候就会 callback 并得到 2 个的第一个值, 如果 2 个不平均, 比如一个 next 了 10 次, 另一个 next 2 次, 那么 callback 就只有 2 次.
等第 2 个 next 第 3 次时, callback 就会得到 第1和第2个的第3次 next 的值.
10. scan
https://ithelp.ithome.com.tw/articles/10187882
就是 js 的 reduce, 区别在于它总是返回 observable. 每一次 next 触发, callback 都可以获取上一次的值 + 现在的值做一些处理, 返回下一个值.
还有一个重点就是 第一次的 emit 会直接 skip 掉 scan 因为这时候还没有 prev value, 通过第二个参数就可以放入初始值了.
这个初始值是不会触发的,不怕。
11. buffer
https://ithelp.ithome.com.tw/articles/10187882
buffer, bufferTime, bufferCount
它用于累积 next 等待另一个 obserable next 的时候才触发 callback
o1.pipe(buffer(o2))... o2 next 的时候 o1 的 callback 才触发, 并且返回期间所有的 o1 next 值.
12. delay & delayWhen
https://ithelp.ithome.com.tw/articles/10187999
delay(1000) 就是延迟 1 秒咯, 如果我们要每一次都不用的话.
就用 delayWhen(v => empty().delay(v + 1000)); 必须返回一个 observable
13.debounce & debounceTime
https://ithelp.ithome.com.tw/articles/10188121
它和 buffer 有点像, 都是会累积值, 但是区别在于, 当一个新值被 next 进来, 它会把之前的值释放掉, 并且时间从新开始算.
14. throttle & throttleTime
https://ithelp.ithome.com.tw/articles/10188121
它用于限制一个时间内, 最高触发的频率. 比如 throttleTime(1000) 就限制了一秒内不管 next 几次, 只有第一次会 callback 往后的都不会, 直到下一秒开始.
14.1 auditTime
它和 debounceTime 很像,只是 debounceTime 会 clear 掉上一次,而这个不会
3个分别的使用 :
用户 keydown 时我想监听
1. debounceTime 1000, 用户连续 keydown 我等 1 秒, 一秒中内用户又 keydown 了,我重新记时,再等 1 秒... 一直到用户 1 秒内再也没有 keydown 我才触发
2. auditTime 1000 用户连续 keydown, 我等 1 秒,一秒中内用户又 keydown 了, 但我 "不" 重新记时了, 1 秒后我就触发, 也就是说, 用户 1 秒内按多少次,我都当成 1 次 并且在 1 秒后才触发.
3. throttleTime 1000, 用户连续 keydown, 我直接触发, 一秒中内用户又 keydown 了, 我不理,直到 1 秒后
特色
debounceTime 重新几时, 一直不触发
auditTime 等..触发
throttleTime 触发...等
15.distinct, distinctUntilChanged
https://ithelp.ithome.com.tw/articles/10188194
它就是我们熟悉的 distinct, 如果值相同就忽略 callback
distinct((x) => { return x.value }); 可以提供一个取值的方法,对比估计是用 ===
distinct 会把所有的值都存起来做对比, distinctUntilChanged 的区别是它只会存最后一次的值做对比.
16.catchError, retry, retryWhen, repeat
https://ithelp.ithome.com.tw/articles/10188263
catchError 是捕获错误,
catchError((error, obs) => obs); 返回第 2 个参数 obs 可以实现从跑.
retry 就是做了上述的事情, 而 retry 多了一个可以指定次数, retry(3) 从试 3 次
retryWhen(errorObs => errorObs.delay(1000)) retryWhen 可以让我们操作更多,比如间隔多久才 retry 下一次等等.
repeat 和 retry 是一样的,区别在于 retry 必须在 error 时才会有, 而 repeat 则是不管有没有 error 都会执行.
17. ReplaySubject, BehaviorSubject
behavior 代表一个保持值的 subject, 一旦订阅马上会触发 callback 并获取到最新的值. 即便不订阅也可以调用 .value 来获取当前值.
replay 会缓存之前的 next 一旦新的订阅加入,就会 playback 之前所有的 next 值.
18. race
race(s1, s2, s3) 的意思是, 哪一个先触发,那么之后我就 watch 这一个罢了,另外 2 个 subject 就不理会了。
19. mapTo
对比 map, mapTo 的参数是一个值,而不是获取值的方法, 所以值就只有一个,要留意哦。
rxjs 在处理 dom 事件时是非常好用的.
步骤一般上是 获取所有 element, 建立所有的 event
然后就是各做 rxjs operator 对 event 和 element 的处理.
参考 : https://ithelp.ithome.com.tw/articles/10187756
更新 : 2017-10-14
更新 2017-05-17
今天被 toPromise 给骗了.
我一直以为, 所有的 "流" 都可以轻松的转成 await stream.toPromise();
后来我发现有个流一直没有反应
let subject = new BehaviorSubject('a'); let o = subject.asObservable(); o.toPromise().then(() => console.log('pro')); //不会跑 setTimeout(()=> { subject.next('haha'); //subject.complete(); }, 1000);
上网找了一下才发现,原来 toPromise().then 必须是 completed 才会跑
所以上面的 subject.complete() 必须要打开才行. 这也意味着 toPromise 只能用在一次的 async 中, 如果是要持续 subscribe 的情况下请使用 .subscribe()
更新 2017-04-02
Subject 的主要功能就是观察者模式.
我们可以随时写入值,完全自己操控.
但是有时候我们希望它依赖于其它 stream 那么我们使用 connect
let s1 = new Subject(); let o1 = s1.asObservable(); //我们想以来的 stream let s2 = new Subject(); s2.subscribe(v => console.log(v)); o1.subscribe(v => s2.next(v),v => s2.error(v)); //第1种写法,超麻烦 o1.subscribe(s2); //第2种,可是我没有要马上 subscribe 的话呢 ? let connector = o1.multicast(s2); //第3种 connector.connect(); s1.next("value");
更新 2017-03-31
好文,力推 : http://ithelp.ithome.com.tw/articles/10189028?sc=iThomeR
再谈谈 cold & hot
observeable 是 default cold 的.
code 的意思是说, 当有多个 subscribe 时,每一个都是一条独立的链.
比如 http 多个 subscribe 的话,你会发现你的 request 会发了好几个.
hot 的意思则是每个 subscribe 共享一个链, 不管你什么之后插入subscribe 你都不会从新开始.
把一个 cold 变成 hot 的方法是使用 Subject 充当中间人.
具体看这 3 篇就明白了
http://ithelp.ithome.com.tw/articles/10188633
http://ithelp.ithome.com.tw/articles/10188677
http://ithelp.ithome.com.tw/articles/10188750
这里介绍一下 ReplaySubject
Subject.subscirbe() , 不会马上执行, 因为要等待下一个 Subject.next
BehaviorSubject.subscribe() , 马上执行, 因为里面一定会有值.
ReplaySubject.subscribe(), 不一定马上执行,如果曾经 .next 过才会执行
multicast, refCount,publish,share 的目的就是把 cold 转换成 hot .
其原理就是使用了 Subject 系列.
multicast 后来被 publish 取代了. publish 对应 subject 所以有 publishBehavior, pulishReplay
refCount 是 connect 的意思,就是把 observer 链接上 subject 的动作.
由于 publish().refCount() 太经常用到,所以发明了 share 写的更快了嘻嘻。
在使用 ng 的 http 时要注意. observable 是 cold 的。但很多情况下我们更希望它是 hot 的.
每一次的 subscribe 应该只返回同一个结果, 而这个 http 只发一次请求.
这时我们需要这样写 : http.get().publishReplay(1).refCount()
publishReplay(1) 之后的每一个 subscribe 都会得到同一个资料了.
更新 : 2017-03-27
什么时候需要 unsubscribe ?
http 不需要, router param 也不需要.
下面说说几个情况
1. Subject.complete 之后, 所有的 subscribe 都不会再触发, 新的 subscribe 也加不进来了.
所以如果我们知道订阅的 Subject 之后会被 complete 那么我们可以无需担心 unsubscribe 的问题
2. 使用 async/await toPromise 可以避开 unsubscribe 的问题.
3. 可以使用 .first(判断) 表示什么时候开始拿然后停 (比如一个值需要等待 ajax)
4. 使用 takeWhile(判断) 来决定什么时候取消订阅.
5. 使用 OnDestroy
更新 : 2017-03-18
2016-09-23
RxJS 博大精深,看了好几篇文章都没有明白.
范围牵扯到了函数响应式开发去了... 我对函数式一知半解, 响应式更是第一次听到...
唉...不过日子还是得过...混着过先呗
我目前所理解的很浅, 大致上是这样的概念.
1.某些场景下它比 promise 好用, 它善于过滤掉不关心的东西.
2.它是观察者模式 + 迭代器模式组成的
3.跟时间,事件, 变量有密切关系
4.世界上有一种东西叫 "流" stream, 一个流能表示了一段时间里,一样东西发生的变化.
比如有一个值, 它在某段时间里从 "我" 变成 "你" 再变成 "他".
而我们可以对这个流进行观察,所以只要它发生变化,我们就会发现然后做任何事情。
5.站在游览器的角度, 服务器推送数据过来, 用户操作界面, timer 都是我们关心的流.
好,来看例子.
我们通过 new Subject 来创建流. 也可以使用 new EventEmitter 或者 BehaviorSubject. 这些都继承了 Subject
EventEmitter 是 ng2 提供的
BehaviorSubject 可以填入初始值
import { Subject } from "rxjs/Subject"; private textEmitter: Subject<string> = new Subject();
要改变流中的值,我们使用 .next(value), 这个是迭代器的概念咯
keyup(value : string) { this.textEmitter.next(value); }
那么订阅是这样的
ngOnInit() { this.text$ = this.textEmitter .debounceTime(500) .distinctUntilChanged() .switchMap(v => this.getDataAsync(v)); this.text$.subscribe((value) => { console.log(value); }); }
input keyup 性能优化, 我们通常会写一个 timeout + cleartimeout 的方式, 这个 debounceTime 就是干这个的
流更新结束后 500ms 才会通知观察者
distinctUntilChanged 是说只有当值和上一次通知时的值不一样的时候才通知观察者
.map 和 .switchMap 都是用来对值进行处理的, 这个和 array.map 概念是一样的
而 .map 和 .switchMap 的区别是 .swichMap 处理那些返回 Observeable 的值
getDataAsync(value : string): Observable<string> { let subject = new Subject(); setTimeout(() => { console.log("after 2second"); subject.next(value + "final"); }, 2000); return subject; }
如果我们使用 map 的话,它会直接返回 "subject" 这个对象, 而如果用 switchMap 它会返回这个 subject 对象的响应值.
<input type="text" #input (keyup)="keyup(input.value)" /> <p>{{ text$ | async }}</p>
ng2 提供了一个 async Pipe, 它会监听左边这个 text$ stream. 后面加一个 $ 符号通常用来表明这是一个 stream.
还有一个常用的功能是 combineLatest
就是可以同时监听多个流,只要其中一个有变动,那么所有的最新值都会发布出去, 可以用来实现依赖属性.
这里需要注意一点 combineLatest 的所有流都必须有值, 不可以是一个从来都没有 next 过的 Observable 不然它就不会运行了.
最简单的方法是使用 observable.startWith(null) 让它有一个值.
@Component({ selector: "compute-property", template: ` <input type="text" #input1 (keyup)="text1.next(input1.value)" /> <input type="text" #input2 (keyup)="text2.next(input2.value)" /> {{ result$ | async }} ` }) export class ComputePropertyComponent implements OnInit { text1: BehaviorSubject<string> = new BehaviorSubject<string>("a"); text2: BehaviorSubject<string> = new BehaviorSubject<string>("b"); result$: Observable<string>; constructor() {} ngOnInit() { this.result$ = Observable.combineLatest(this.text1, this.text2).map(values => { return values[0] + " " + values[1]; }); } }
还有 bufferCount, bufferTime 也是常用到
text: Subject<number> = new Subject<number>(); ngOnInit() { this.text.bufferCount(2) .subscribe(v => console.log(v)); //[v1,v2] 存够 count 了就发布 this.text.bufferTime(2000) .subscribe(v => console.log(v)); //[v1,v2,...]把 2 秒内的所有 next value 放进来 }
Observable.of 可以简单的返回一个默认值
Observable.of<string>("").subscribe(v => console.log(v));
rxjs 整个文档非常大,要按需加载.
通常做法是为项目开一个 rxjs-operators.ts
import 'rxjs/add/observable/throw'; import 'rxjs/add/observable/combineLatest'; import 'rxjs/add/observable/from'; import 'rxjs/add/observable/of'; import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/debounceTime'; import 'rxjs/add/operator/distinctUntilChanged'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/switchMap'; import 'rxjs/add/operator/toPromise'; import 'rxjs/add/operator/startWith'; import 'rxjs/add/operator/bufferCount'; import 'rxjs/add/operator/bufferTime';
放入常用的方法
然后再 app.module.ts 里面导入它
import './rxjs-operators';
Hot or cold , share or not
refer :
http://blog.thoughtram.io/angular/2016/06/16/cold-vs-hot-observables.html
http://blog.csdn.net/tianjun2012/article/details/51351823
1. by default, observable is not share.
let sub = new Subject(); let obs = sub.map(v => { console.log("ajax call"); }); obs.subscribe(v => console.log("subscribe 1")); obs.subscribe(v => console.log("subscribe 2")); sub.next("value");
ajax 发了 2 次. angular2 的 Http 也是 not share 哦.
所以当我们有多个 subscribe 的时候要想一想是否我们需要 share
let obs = sub.map(v => { console.log("ajax call"); }).share();
调用一个 share 方法就可以了,或者是
let obs = sub.map(v => { console.log("ajax call"); }).publish().refCount();
效果是一样的.
by default, observable is cold.
意思是说只有在 subscribe 出现了以后才会启动. ( 当第一个 subscribe 出现时, observable 就会立刻启动了哦 )
let sub = new Subject(); let obs = sub.map(v => { console.log("ajax call"); }); sub.next("aaa"); //obs.subscribe(v => console.log("subscribe 1")); //obs.subscribe(v => console.log("subscribe 2"));
ajax 不会触发.
如果我们希望它在没有 subscribe 的情况下触发的话, 可以这样写.
let sub = new Subject(); let obs = sub.map(v => { console.log("ajax call"); }).publish(); obs.connect(); sub.next("aaa");
至于什么情况下使用哪一种,我还没有实战,以后再说.
多一个例子解释:
let obs = Observable.create(observer => { console.log("observer run"); observer.next(Date.now()); }); obs.subscribe(v => console.log("1st subscriber: " + v)); obs.subscribe(v => console.log("2nd subscriber: " + v)); //observer run //1st subscriber: 1474649902498 //observer run //2nd subscriber: 1474649902501
no share. 所以 observer run 了 2 次.
let obs = Observable.create(observer => { console.log("observer run"); observer.next(Date.now()); }).share(); obs.subscribe(v => console.log("1st subscriber: " + v)); obs.subscribe(v => console.log("2nd subscriber: " + v)); //observer run //1st subscriber: 1474650049833
share 了, 所以 observer only run 1 次.
cold, 所以当第一个 subcribe 出现后 observer 立刻运行 -> .next 更新了 value -> 第一个 subcribe callback 被调用 -> 整个过程结束 -> 然后第2个 subcribe 注册 .. 由于是 share 所以 observer 没有载被触发. 第2个 subscribe callback 没有被调用.
延后触发的做法 :
let obs = Observable.create(observer => { console.log("observer run"); observer.next(Date.now()); }).publish(); obs.subscribe(v => console.log("1st subscriber: " + v)); obs.subscribe(v => console.log("2nd subscriber: " + v)); obs.connect(); //observer run //1st subscriber: 1474650370505 //2nd subscriber: 1474650370505
可以看到 .publish() 之后, subscribe 不再能激活 observer 了,而必须手动调用 .connect() 才能激活 observer.
这几个例子只是为了让你了解它们的玩法.
小结:
observer default is cold and not share.
cold 表示只有 subscribe 出现 observer 才会被激活.
not share 表示每一个 subscribe 都会激活 observer 链.
常用 :
1. finally 的使用
import 'rxjs/add/operator/finally';
this.http.get( "http://localhost:58186/api/products", { headers: new Headers({ "Accept": "application/json" })} ).finally(() => { console.log("finally"); //不管 success or error 最后都会跑这个 }).subscribe(response => { console.log("success"); }, response => { console.log("fail"); }, () => { console.log("success final"); }); //result : //success -> success final -> finally //fail -> finally
2. 错误处理 throw catch
import 'rxjs/add/operator/catch';
import { Observable } from "rxjs/Observable";
import 'rxjs/add/observable/throw';
this.http.get( "http://localhost:58186/api/products", { headers: new Headers({ "Accept": "application/json" }) }) .map(r => r.json()) .catch((r) => { if ("1" == "1") { //do something ... return null; //catch 了在返回真确 } else { return Observable.throw("error"); //catch 了继续返回错误 } }) .subscribe( r => console.log(r), r => { console.log("fail") } );
3. previous / current value
用 .pairwise()
let userSubject = new BehaviorSubject<string>("default value"); let user$ = userSubject.asObservable().pairwise(); user$.subscribe(([before, after]) => { console.log(before), console.log(after); }); userSubject.next("super"); userSubject.next("ttc"); //result : //["default value","super"] //["super","ttc"]