RxJS---转载
摘自 此处:https://segmentfault.com/a/1190000012252368
导入方式
避免使用 import { Observable } from 'rxjs'
这种方式导入,这会导入整个rxjs
库,按需导入的方式如下:import { Observable } from 'rxjs' //导入类
Rx
JS团队设计了以下规则来帮助JavaScript开发人员重构
import
路径:
-
rxjs
: 包含创建方法,类型,调度程序和工具库。import { Observable, Subject, asapScheduler, pipe, of, from, interval, merge, fromEvent } from 'rxjs';
-
rxjs/operators
: 包含所有的管道操作符import { map, filter, scan } from 'rxjs/operators';
-
rxjs/webSocket
: 包含websocket subject实现.import { webSocket } from 'rxjs/webSocket';
-
rxjs/ajax
: 包含Rx ajax实现.import { ajax } from 'rxjs/ajax';
-
rxjs/testing
: 包含RxJS的测试工具库.import { TestScheduler } from 'rxjs/testing';
subscribe
subscribe
完整的函数签名如下:
ob.subscribe({
next: d => console.log(d),
error: err => console.error(err),
complete: () => console.log('end of the stream')
})
直接给subscribe
传入一个函数会被当做是next
函数。这个完整的包含3个函数的对象被称为observer
(观察者),表示的是对序列结果的处理方式。
next
表示数据正常流动,没有出现异常;error
表示流中出错,可能是运行出错,http
报错等等;complete
表示流结束,不再发射新的数据。在一个流的生命周期中,error
和complete
只会触发其中一个,可以有多个next
(表示多次发射数据),直到complete
或者error
。
创建可观察序列
点击此处查看示例
Observable.of(...args)
Observable.of()
可以将普通JavaScript数据转为可观察序列
Observable.fromPromise(promise)
将Promise
转化为Observable
Observable.fromEvent(elment, eventName)
从DOM
事件创建序列,例如Observable.fromEvent($input, 'click')
Observable.ajax(url | AjaxRequest)
发送http
请求,AjaxRequest
Observable.create(subscribe)
这个属于万能的创建方法,一般用于只提供了回调函数的某些功能或者库,在你用这个方法之前先想想能不能用RxJS
上的类方法来创建你所需要的序列
合并序列
const ob1 = Observable.ajax('api/detail/1');
const ob2 = Observable.ajax('api/detail/2');
...
const obs = [ob1, ob2...];// 分别创建对应的HTTP请求。
- N个请求按顺序串行发出(前一个结束再发下一个)
Observable.concat(...obs).subscribe(detail => console.log('每个请求都触发回调'));
- N个请求同时并行发出,对于每一个到达就触发一次回调
Observable.merge(...obs).subscribe(detail => console.log('每个请求都触发回调'));
- N个请求同时发出并且要求全部到达后合并为数组,触发一次回调
Observable.forkJoin(...obs).subscribe(detailArray => console.log('触发一次回调'));
使用RxJS
实现搜索功能
搜索是前端开发中很常见的功能,一般是监听<input />
的keyup
事件,然后将内容发送到后台,并展示后台返回的数据。
var text = document.querySelector('#text');
var inputStream = Rx.Observable.fromEvent(text, 'keyup') //为dom元素绑定'keyup'事件
.debounceTime(250) // 防抖动(防止重复的请求)
.pluck('target', 'value') // 取值
.switchMap(url => Http.get(url)) // 将当前输入流替换为http请求
.subscribe(data => render(data)); // 接收数据
RxJS
能简化你的代码,它将与流有关的内部状态封装在流中,而不需要在流外定义各种变量来以一种上帝视角控制流程。
一些操作符
合并 forkJoin
, merge
, concat
创建 of
, from
, fromPromise
, fromEvent
, ajax
, throw
实例操作符(对流中的数据进行处理或者控制流程)map
, filter
,switchMap
, toPromise
, catch
, take
, takeUntil
, timeout
, debounceTime
, distinctUntilChanged
, pluck
。
Map与switchmap\flatmap
let stream = Observable.interval(1000).take(10);
return stream.map(n => Observable.timer(500).map(() => n));
这里stream
会返回一个Observable
而不是数字。
如果我想要拿到那些数字,我该怎么办?
let stream = Observable.interval(1000).take(10);
return stream.flatMap(n => Observable.timer(500).map(() => n));
这里使用了flatMap
而不是map
。flatMap
将响应数据“打平”,也就是说把映射后新的Observable
转化为了数据流,订阅之后会获得这个新Observable
发射的数据,而不是Observable
本身。
译者注:flatMap
有一个很适用的场景,就是搜索框。在用户输入一串字符后,将其发送到服务器并获取搜索结果,这里就涉及到两个Observable
。
Observable
.fromEvent($input, 'keyup')
.flatMap(text => getHttpResponse(text))
.subscribe(data => console.log(data))
使用flatMap
就可以直接获取到新的Observable
返回的数据。但是这里存在一个问题,如果用户有多次输入,由于网络原因可能会发生前一次响应时间比后一次长的情况,这时后一次的结果就被覆盖了。flatMapLatest
可以解决这个问题。如果之前的Observable
还没有未触发,而又收到了新的Observable
,flatMapLatest
会取消之前的Observable
,只处理最新收到的Observable
,这样就保证了处理请求的先后顺序,flatMapLatest
在RxJS 5.x
中已更名为switchMap
。
forkJoin, zip, combineLatest之间的区别
forkJoin
用forkJoin
合并的流,会在每个被合并的流都发出结束信号时发射一次也是唯一一次数据。假设我们有两个流:
1 const ob1 = Rx.Observable.interval(1000).map(d => `ob1:${d}`).take(3);
2 const ob2 = Rx.Observable.interval(2000).map(d => `ob2:${d}`).take(2);
3
4 Rx.Observable.forkJoin(ob1, ob2).subscribe((data) => console.log(data));
5 // ["ob1:2", "ob2:1"]
ob1
会在发射完第三个数据时停止发射,ob2
会在发射完第二个数据时停止,而forkJoin
合并后的流会等到ob1
和ob2
都结束时,发射一次数据,也就是触发一次subscribe
里的回调,接收到的数据为ob1
和ob2
发射的最后一次数据的数组。
zip
zip
工作原理如下,当每个传入zip
的流都发射完毕第一次数据时,zip
将这些数据合并为数组并发射出去;当这些流都发射完第二次数据时,zip
再次将它们合并为数组并发射。以此类推直到其中某个流发出结束信号,整个被合并后的流结束,不再发射数据。
const ob1 = Rx.Observable.interval(1000).map(d => `ob1:${d}`).take(3);
const ob2 = Rx.Observable.interval(2000).map(d => `ob2:${d}`).take(2);
Rx.Observable.zip(ob1, ob2).subscribe({
next: (data) => console.log(data),
complete: () => console.log('complete')
});
// ["ob1:0", "ob2:0"] ob1等待ob2发射数据,之后合并
// ["ob1:1", "ob2:1"] 此时ob2结束,整个合并的流也结束
// "complete"
zip
和forkJoin
的区别在于,forkJoin
仅会合并各个子流最后发射的一次数据,触发一次回调;zip
会等待每个子流都发射完一次数据然后合并发射,之后继续等待,直到其中某个流结束(因为此时不能使合并的数据包含每个子流的数据)。
combineLatest
combineLatest
与zip
很相似,combineLatest
一开始也会等待每个子流都发射完一次数据,但是在合并时,如果子流1在等待其他流发射数据期间又发射了新数据,则使用子流最新发射的数据进行合并,之后每当有某个流发射新数据,不再等待其他流同步发射数据,而是使用其他流之前的最近一次数据进行合并。
const ob1 = Rx.Observable.interval(1000).map(d => `ob1:${d}`).take(3);
const ob2 = Rx.Observable.interval(2000).map(d => `ob2:${d}`).take(2);
Rx.Observable.combineLatest(ob1, ob2).subscribe({
next: (data) => console.log(data),
complete: () => console.log('complete')
});
// ["ob1:1", "ob2:0"] ob1等待ob2发射,当ob2发射时ob1已经发射了第二次数据,使用ob1的第二次数据
// ["ob1:2", "ob2:0"] ob1继续发射第三次也是最后一次数据,ob2虽然还未发射,但是可以使用它上一次的数据
// ["ob1:2", "ob2:1"] ob2发射第二次也是最后一次数据,使ob1上一次的数据。
// "complete"