RxJS 是 ReactiveX 编程理念的 JavaScript 版本。ReactiveX 来自微软,它是一种针对异步数据流的编程。简单来说,它将一切数据,包括HTTP请求,DOM事件或者普通数据等包装成流的形式,然后用强大丰富的操作符对流进行处理,使你能以同步编程的方式处理异步数据,并组合不同的操作符来轻松优雅的实现你所需要的功能。
RxJS 是一个使用可观察序列组成异步和基于事件的程序的库。它提供了一种核心类型,Observable,卫星类型(Observer,Schedulers,Subjects)和受Array#extras(map,filter,reduce,every等)启发的运算符,允许将异步事件作为集合处理。
Observable
: 表示一个可调用的未来值或事件的集合Observer
: 一个回调函数的集合,它知道如何去监听由 Observable 提供的值Subscription
: 表示 Observable 的执行,主要用于取消 Observable 的执行Operators
: 采用函数式编程风格的纯函数,例如 map、filter、concat、flatMap
等这样的操作符,用来处理集合Subject
: 相当于 EventEmitter,并且是将值或事件多路推送给多个 Observer 的唯一方式Schedulers
: 用来控制并发并且是中央集权的调度员,允许我们在发生计算时进行协调,例如 setTimeout 或 requestAnimationFrame 或其他// 与之前版本相比,所有的操作符在 pipe 管道中执行
import { of } from 'rxjs';
import { map } from 'rxjs/operators'
of([1, 2]).pipe(map(item => item * 2))
Observable
: RxJS 最基本的构建块of
: 将参数转换为可观察序列from
: 从数组,类数组对象,Promise,可迭代对象或类似 Observable 的对象创建 ObservablefromEvent
: 创建一个 Observable,它发出来自给定事件目标的特定类型的事件interval
: 创建一个 Observable,指定每隔多长时间发出一个序列号range
: 创建一个 Observable,它发出指定范围内的一系列数字timer
: 创建一个 Observable,在 dueTime 此后的每个 period 时间之后开始并发出不断增加的数字import { Observable, of, from, fromEvent } from 'rxjs';
const observable = new Observable(subscriber => {
subscriber.next(1);
subscriber.next(2);
subscriber.next(3);
setTimeout(() => {
subscriber.next(4);
subscriber.complete();
}, 1000);
});
of([1, 2])
of(1, 2, 3)
of(true)
of(null)
from(fetch('/api/endpoint'))
fromEvent(document.getElementById('my-element'), 'mousemove');
interval(1000)
range(0, 10)
mapTo
: 对数据源每项都转换为指定的值/其他map
: 指定一个函数处理源可观测值所发出的每个值,并将结果值作为新的可观测值发出scan
: 对 Observable 值的进行累积操作mergeMap
: 数据遍历映射,并依次拼接。第二个参数表示同时订阅的最大输入 Observable 数。即并发数switchMap
: 返回一个 Observable,它根据您应用的函数发出项目,该函数提供给源 Observable 发出的每个项目,该函数返回一个(所谓的“内部”)Observable。每次观察其中一个内部 Observable 时,输出 Observable 开始发出该内部Observable 发出的项。当发出新的内部 Observable 时,switchMap 停止从先前发出的内部Observable中发出项目并开始从新的Observable中发出项目bufferTime
: 建立一个缓冲区(Observable),将数据源收集到缓冲区中,然后根据时间参数n对数据源进行分组,在同一n毫秒下的数据为一组,然后把分组好的数据集合发射出来concatMap
: 将每个源值投影到 Observable,Observable 在输出 Observable 中合并,在合并下一个完成之前以序列化方式等待每个值。concatMap 相当于将 mergeMap 并发参数设置为 1pluck
: 根据 key 值提取 Observable 所产生的值的 value,匹配不上返回 undefinedimport { of, interval } from 'rxjs';
import { mapTo, map, scan, mergeMap, concatMap, bufferTime, take } from 'rxjs/operators'
of(1, 2, 3).pipe(scan((acc, one) => acc + one, 0)) // 返回 1 3 6
of('a', 'b', 'c').pipe(
mergeMap(x => interval(1000).pipe(
map(i => x + i)),
take(4) // 每秒输出 'ai bi ci' i未从0到3
)
);
// 每次点击都会返回一个新的 interval
fromEvent(document, 'click').pipe(
switchMap(data => {
return interval(1000).pipe(
mapTo(data),
take(data)
)
})
)
// 每个3秒 发出一个数据的数组
interval(1000).pipe(bufferTime(3000))
of(1, 2, 3).pipe(concatMap(data => of(data)))
// sex1 sex2
result = of(
{
id: '1',
name: 'name1',
obj: {
sex: 'sex1'
}
},
{
id: '2',
name: 'name2',
obj: {
sex: 'sex2'
}
}
).pipe(pluck('obj', 'sex'))
take
: take 返回一个 Observable,它只 count 发出源 Observable 发出的第一个值。如果源发出的 count 值小于值,则会发出其所有值。之后,无论源是否完成,它都会完成takeUntil
: 发出源 Observable 发出的值,直到notifier Observable 发出值后停止filter
: 过滤源Observable发出的项目,只发出满足指定谓词的项目debounceTime
: 延迟源 Observable 发出的值,但如果新值到达源 Observable,则会丢弃先前待处理的延迟发射。这是一个速率限制运算符,因为不可能在任何持续时间窗口中发出多个值 dueTime,但它也是一个类似延迟的运算符,因为输出发射不会像它们那样同时发生来源 ObservabledistinctUntilChanged
: 把相同的元素过滤掉,如果提供了比较器功能,则将为每个项目调用它以测试是否应该发出该值。如果未提供比较器功能,则默认使用相等性检查import { of, interval } from 'rxjs';
import { filter } from 'rxjs/operators'
of(1, 2, 3, 4).pipe(filter(data => data > 2)) // 3 4
// 每秒发送一个序列号,直到鼠标点击之后停止
interval(1000).pipe(takeUntil(fromEvent(document, 'click')))
// 每次点击 延迟3秒发出
fromEvent(document, 'click').pipe(debounceTime(3000))
// 1 2 1 2 3 4
of(1, 1, 2, 2, 2, 1, 1, 2, 3, 3, 4).pipe(distinctUntilChanged())
merge
: 创建一个输出 Observable,它同时发出每个给定输入 Observable 的所有值zip
: 组合多个 Observable 以创建一个 Observable,其值根据其每个输入 Observable 的值按顺序计算,如果最后一个参数是函数,则此函数用于根据输入值计算创建的值。否则,返回一个输入值数组concat
: 创建一个输出 Observable,它从给定的 Observable 顺序发出所有值,然后继续下一个startWith
: 返回一个 Observable,它在开始发出源 Observable 发出的项之前发出您指定为参数的项。首先按顺序发出其参数,随后发出源 Observable 的值combineLatest
: 组合多个 Observable 以创建一个 Observable,其值是根据每个输入 Observable 的最新值计算的。只要任何输入 Observable 发出一个值,它就会使用所有输入中的最新值计算公式,然后发出该公式的输出withLatestFrom
: 将源Observable与其他Observable组合以创建一个Observable,其值仅根据源发出的值从每个值的最新值计算。每当源Observable发出一个值时,它使用该值加上来自其他输入Observable的最新值计算公式,然后发出该公式的输出。import { merge, interval, zip, concat, range, timer, combineLatest } from 'rxjs'
import { take } from 'rxjs/operators'
// 合并 3个 Observable,但只有 2 个同时运行
merge(
interval(1000).pipe(take(10)),
interval(2000).pipe(take(6)),
interval(500).pipe(take(10)),
2
)
// [1, "a", true] [2, "b", false]
zip(of(1, 2, 3), of('a', 'b', 'c', 'd'), of(true, false))
// 先每隔一秒输出一个序号 0 ~ 4 随后一次性输出 1~10
concat(interval(1000).pipe(take(4)), range(1, 10))
// first -> second -> from source
of('from source').pipe(startWith('first', 'second'))
// [0,0] [1,0] [1,1] [2,1] [2,2] [3,2] [3,3] [4,3] [4,4]
combineLatest(timer(0, 1000).pipe(take(5)), timer(500, 1000).pipe(take(5)))
// [Event, (当前 interval 的序号,序号一开始每隔一秒加一,点击时候当前是几就显示几)]
fromEvent(document, 'click').pipe(withLatestFrom(interval(1000)))
tap
: 对源 Observable 上的每个发射执行副作用,但返回与源相同的 Observable。截取源上的每个发射并运行一个函数,但只要不发生错误,就返回一个与源相同的输出import { fromEvent } from 'rxjs'
import { tap, map } from 'rxjs/operators'
// 将每次单击映射到该单击的clientX位置,同时还记录click事件
fromEvent(document, 'click').pipe(
tap(ev => console.log(ev)),
map((ev: any) => ev.clientX)
)
多播是一个术语,它用来描述由单个 observable 发出的每个通知会被多个观察者所接收的情况。一个 observable 是否具备多播的能力取决于它是热的还是冷的。
热的和冷的 observable 的特征在于 observable 通知的生产者是在哪创建的。热的 Vs 冷的 Observables 差异可以归纳如下:
有些时候,需要冷的 observable 具有多播的行为,RxJS 引入了 Subject 类使之成为可能。
Subject 即是 observable,又是 observer (观察者)。通过使用观察者来订阅 subject,然后 subject 再订阅冷的 observable,可以让冷的 observable 变成热的。这是 RxJS 引入 subjects 的主要用途
share
: 返回一个新的 Observable,它可以多播(共享)原始的 Observable。只要至少有一个订阅者,此Observable将被订阅并发送数据。当所有订阅者都取消订阅时,它将取消订阅源 Observable。因为 Observable 是多播,所以它构成了流 hot。这是别名 multicast(() => new Subject()), refCount()
publish
: 共享源 observable 并通过调用 connect 方法使其变成热的。
multicast
: 所有的多播操作符都是以这个为基础封装而来import { interval, Subject, ReplaySubject } from 'rxjs'
import { take, share, publish, multicast } from 'rxjs/operators'
// 冷 0 1 2 3 0 4 1 5 2 7 3 4 5 6 7
result = interval(1000).pipe(take(8))
result.subscribe(data => console.log(data))
setTimeout(() => {
result.subscribe(data => console.log(data))
}, 3000)
// share 热 0 1 2 3 3 4 4 5 5 6 6 7 7
result = interval(1000).pipe(
take(8),
share()
)
result.subscribe(data => console.log(data))
setTimeout(() => {
result.subscribe(data => console.log(data))
}, 3000)
// publish 未指定 selector
result = interval(1000).pipe(
take(8),
publish()
)
result.subscribe(data => console.log(data))
setTimeout(() => {
result.subscribe(data => console.log(data))
result.connect()
}, 3000)
// publish 指定 selector,表现等同于 share
result = interval(1000).pipe(
take(8),
publish(multicasted$ => {
// 在这里做一些额外的操作
return multicasted$
})
)
result.subscribe(data => console.log(data))
setTimeout(() => {
result.subscribe(data => console.log(data))
}, 3000)
// multicast 使用 Subject
result = interval(1000).pipe(
take(8),
multicast(() => new Subject())
)
result.subscribe(data => console.log(data))
setTimeout(() => {
result.subscribe(data => console.log(data))
result.connect()
}, 3000)
// multicast 使用 Subject 指定 selector
result = interval(1000).pipe(
take(8),
multicast(
() => new Subject(),
multicasted$ => {
return multicasted$
}
)
)
result.subscribe(data => console.log(data))
setTimeout(() => {
result.subscribe(data => console.log(data))
}, 3000)
// 使用 ReplaySubject
result = interval(1000).pipe(
take(8),
multicast(
() => new ReplaySubject(2),
multicasted$ => {
return multicasted$
}
)
// 输入的值表示缓冲的值。其他的订阅则是在其订阅的时候一次性返回缓冲的值
// 0 1 2
// 1 2 3 3
// 2 3 4 4 4
// 5 5 5
// ...
)
result.subscribe(data => console.log(data))
setTimeout(() => {
result.subscribe(data => console.log(data))
}, 3000)
setTimeout(() => {
result.subscribe(data => console.log(data))
}, 4000)
获取数据需要订阅 Observable
对于冷的 Observable
每次订阅都会时候才会被创建,也就是每次都是一个新的 Observable
从 6 版本往后,所有的操作符需要再 pipe
中执行
Observable
可以和 Promise
互相转换
of[1, 2, 3].toPromise()
from(new Promise())
使用 catchError
处理了错误之后,后续的订阅者就接收不到 error 信息。需要再返回一个新的 error
import { Injectable } from '@angular/core'
import { HttpClient } from '@angular/common/http'
import { Observable, of, throwError } from 'rxjs'
import { catchError, switchMap } from 'rxjs/operators'
import { LoadingService } from './loading.service'
import { NzNotificationService } from 'ng-zorro-antd'
@Injectable({
providedIn: 'root'
})
export class HttpService {
constructor(private http: HttpClient, private loading: LoadingService, private notifition: NzNotificationService) {}
processing(http: Observable<any>): Observable<any> {
this.loading.next(true)
return http.pipe(
switchMap(data => {
this.loading.next(false)
if (data.status === 1) {
return of(data.data)
}
return throwError(data)
}),
catchError((e: any) => {
this.notifition.error('错误', e.message)
return throwError(e)
})
)
}
}
待完善