更新 : 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 都可以获取上一次的值 + 现在的值做一些处理, 返回下一个值.
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 往后的都不会, 直到下一秒开始.
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 值.
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"]