switchMap
签名: switchMap(project: function: Observable, resultSelector: function(outerValue, innerValue, outerIndex, innerIndex): any): Observable
映射成 observable,完成前一个内部 observable,发出值。
如果你想要维护多个内部 subscription 的话, 请尝试 mergeMap
!
此操作符通常被认为是 mergeMap
的安全版本!
此操作符可以取消正在进行中的网络请求!
为什么使用 switchMap
?
switchMap
和其他打平操作符的主要区别是它具有取消效果。在每次发出时,会取消前一个内部 observable (你所提供函数的结果) 的订阅,然后订阅一个新的 observable 。你可以通过短语切换成一个新的 observable来记忆它。
它能在像 typeaheads 这样的场景下完美使用,当有新的输入时便不再关心之前请求的响应结果。在内部 observable 长期存活可能会导致内存泄露的情况下,这也是一种安全的选择,例如,如果你使用 mergeMap 和 interval,并忘记正确处理内部订阅。记住,switchMap
同一时间只维护一个内部订阅,在示例 1中可以清楚出看到这一点。
不过要小心,在每个请求都需要完成的情况下,考虑写数据库,你可能要避免使用 switchMap
。如果源 observable 发出速度足够快的话,switchMap
可以取消请求。在这些场景中,mergeMap 是正确的选择。
示例
示例 1: 每5秒重新启动 interval
( StackBlitz |
jsBin |
jsFiddle )
import { timer } from 'rxjs/observable/timer';
import { interval } from 'rxjs/observable/interval';
import { switchMap } from 'rxjs/operators';
// 立即发出值, 然后每5秒发出值
const source = timer(0, 5000);
// 当 source 发出值时切换到新的内部 observable,发出新的内部 observable 所发出的值
const example = source.pipe(switchMap(() => interval(500)));
// 输出: 0,1,2,3,4,5,6,7,8,9...0,1,2,3,4,5,6,7,8
const subscribe = example.subscribe(val => console.log(val));
示例 2: 每次点击时重置
( StackBlitz |
jsBin |
jsFiddle )
import { fromEvent } from 'rxjs/observable/fromEvent';
import { interval } from 'rxjs/observable/interval';
import { switchMap, mapTo } from 'rxjs/operators';
// 发出每次点击
const source = fromEvent(document, 'click');
// 如果3秒内发生了另一次点击,则消息不会被发出
const example = source.pipe(
switchMap(val => interval(3000).pipe(mapTo('Hello, I made it!')))
);
// (点击)...3s...'Hello I made it!'...(点击)...2s(点击)...
const subscribe = example.subscribe(val => console.log(val));
示例 3: 使用 resultSelector
函数
( StackBlitz |
jsBin |
jsFiddle )
import { timer } from 'rxjs/observable/timer';
import { interval } from 'rxjs/observable/interval';
import { switchMap } from 'rxjs/operators';
// 立即发出值, 然后每5秒发出值
const source = timer(0, 5000);
// 当 source 发出值时切换到新的内部 observable,调用投射函数并发出值
const example = source.pipe(
switchMap(
_ => interval(2000),
(outerValue, innerValue, outerIndex, innerIndex) => ({
outerValue,
innerValue,
outerIndex,
innerIndex
})
)
);
/*
输出:
{outerValue: 0, innerValue: 0, outerIndex: 0, innerIndex: 0}
{outerValue: 0, innerValue: 1, outerIndex: 0, innerIndex: 1}
{outerValue: 1, innerValue: 0, outerIndex: 1, innerIndex: 0}
{outerValue: 1, innerValue: 1, outerIndex: 1, innerIndex: 1}
*/
const subscribe = example.subscribe(val => console.log(val));
示例 4: 使用 switchMap 的倒计时定时器
( StackBlitz |
jsBin |
jsFiddle )
import { interval } from 'rxjs/observable/interval';
import { fromEvent } from 'rxjs/observable/fromEvent';
import { merge } from 'rxjs/observable/merge';
import { empty } from 'rxjs/observable/empty';
import { switchMap, scan, takeWhile, startWith, mapTo } from 'rxjs/operators';
const countdownSeconds = 10;
const setHTML = id => val => (document.getElementById(id).innerHTML = val);
const pauseButton = document.getElementById('pause');
const resumeButton = document.getElementById('resume');
const interval$ = interval(1000).pipe(mapTo(-1));
const pause$ = fromEvent(pauseButton, 'click').pipe(mapTo(false));
const resume$ = fromEvent(resumeButton, 'click').pipe(mapTo(true));
const timer$ = merge(pause$, resume$)
.pipe(
startWith(interval$),
switchMap(val => (val ? interval$ : empty())),
scan((acc, curr) => (curr ? curr + acc : acc), countdownSeconds),
takeWhile(v => v >= 0)
)
.subscribe(setHTML('remaining'));
HTML
<h4>
Time remaining: <span id="remaining"></span>
</h4>
<button id="pause">
Pause Timer
</button>
<button id="resume">
Resume Timer
</button>
相关食谱
其他资源
- switchMap - 官方文档
- 使用 switchMap 开启流 - John Linquist
- 使用 RxJS 的 switchMap 操作符来映射并打平高阶 observables - André Staltz
- 在 RxJS 中,使用 switchMap 作为打平 observables 的安全默认操作符 - André Staltz
源码: https://github.com/ReactiveX/rxjs/blob/master/src/internal/operators/switchMap.ts