Map 操作符之间的差异
在 RxJS 中有多种高级的 map 操作符,如:concatMap、mergeMap、switchMap、exhaustMap,这些高阶 map 操作符与普通的 map 操作符有所不同。普通 map 操作符只是将数据映射为另一个数据,高级 map 操作符是将数据映射为 Observable 对象。
在 RxJS v4 中 mergeMap 叫 flatMap,后来觉得从名称上不好理解 mergeMap 所做的操作,从 v5 开始改名为 mergeMap。同时为了向后兼容,并未废弃 flatMap,flatMap 作为 mergeMap 的一个别名存在;因此,可以用 flatMap 替代 mergeMap。
下面通过一个简单的场景来寻找高级 Map 操作符之间的差异:
提供一些用户 ID,根据这些 ID 查询用户的 Todo List
接下来看下这些 Map 操作符具体如何来实现这个需求:
1 | queryTodo(userID: number): Observable<any> { |
mergeMap
1
2
3
4
5
6const user$ = of(1, 2, 3, 4).pipe(
mergeMap(userID => this.queryTodo(userID)),
tap(user => console.warn(JSON.stringify(user, ["userId"])))
).subscribe(res => {
console.info(res);
});通过截图可以看到,mergeMap 会顺序去请求用户的 Todo List,但最终返回的 Observable 的顺序却为乱序。
mergeMap 是 mergeAll 和 map 操作符的组合。mergeMap 操作符内部产生的 Observable 对象之间是并行的,谁先有结果,则先输出到下游,也就是内部 Observable 产生数据立即传给下游。
concatMap
1
2
3
4
5
6const user$ = of(1, 2, 3, 4).pipe(
concatMap(userID => this.queryTodo(userID)),
tap(user => console.warn(JSON.stringify(user, ["userId"])))
).subscribe(res => {
console.info(res);
});从截图可以看到,返回的 Todo List 是顺序的。
concatMap 是 concatAll 和 map 的组合。concatMap 操作符的内部大概的流程是:产生一个内部 Observable 后,立即订阅,每产生一个数据都传给下游,当前 Observable 完结后取消订阅,然后订阅下一个 Observable。
switchMap
1
2
3
4
5
6const user$ = of(1, 2, 3, 4).pipe(
switchMap(userID => this.queryTodo(userID)),
tap(user => console.warn(JSON.stringify(user, ["userId"])))
).subscribe(res => {
console.info(res);
});可以看到 switchMap 把前面用户的请求都取消了,只保留了最后一个用户的请求。
switchMap 是 switch 和 map 的组合。对于 meigeMap 和 concatMap 而言,内部产生的 Observable 优先级都一样,但在 switchMap 中后产生的 Observable 对象优先级总是比前面的高,也就是最新的 Observable 优先级最高;只有要新的 Observable 产生,那么会立即取消订阅之前的 Observable,然后订阅最新的 Observable。一句话概括就是,只要有新的 Observable 产生,switchMap 会切换到最新的内部 Observable 对象。
exhaustMap
1
2
3
4
5
6const user$ = of(1, 2, 3, 4).pipe(
exhaustMap(userID => this.queryTodo(userID)),
tap(user => console.warn(JSON.stringify(user, ["userId"])))
).subscribe(res => {
console.info(res);
});exhaustMap 操作符只去请求了第一个用户的 Todo List,忽略了其余用户的请求。
exhaustMap 是 exhaust 和 map 的组合。与 switchMap 操作符相反的是,对于 exhaust 操作符而言先产生的 Observable 优先级更高。在前面产生的 Observable 未完结之前,exhaustMap 不会去订阅新产生的 Observable。
将上面代码替换下
1
2
3
4
5
6
7
8const user$ = interval(100).pipe(
take(4),
map(userID => userID + 1),
exhaustMap(userID => this.queryTodo(userID)),
tap(user => console.warn(JSON.stringify(user, ["userId"])))
).subscribe(res => {
console.info(res);
});可以看到第二个用户的请求被忽略了,因为第一个请求耗时 127ms,第二个 Observable 对象是在第一个 Observable 对象完结前产生的。