• switchMap
    • 签名: switchMap(project: function: Observable, resultSelector: function(outerValue, innerValue, outerIndex, innerIndex): any): Observable
  • 映射成 observable,完成前一个内部 observable,发出值。
    • 为什么使用 switchMap
    • 示例
      • 示例 1: 每5秒重新启动 interval
      • 示例 2: 每次点击时重置
      • 示例 3: 使用 resultSelector 函数
      • 示例 4: 使用 switchMap 的倒计时定时器
        • HTML
  • 相关食谱
  • 其他资源

    switchMap

    签名: switchMap(project: function: Observable, resultSelector: function(outerValue, innerValue, outerIndex, innerIndex): any): Observable

    映射成 observable,完成前一个内部 observable,发出值。


    :bulb: 如果你想要维护多个内部 subscription 的话, 请尝试 mergeMap

    :bulb: 此操作符通常被认为是 mergeMap 的安全版本!

    :bulb: 此操作符可以取消正在进行中的网络请求!


    为什么使用 switchMap

    switchMap 和其他打平操作符的主要区别是它具有取消效果。在每次发出时,会取消前一个内部 observable (你所提供函数的结果) 的订阅,然后订阅一个新的 observable 。你可以通过短语切换成一个新的 observable来记忆它。

    它能在像 typeaheads 这样的场景下完美使用,当有新的输入时便不再关心之前请求的响应结果。在内部 observable 长期存活可能会导致内存泄露的情况下,这也是一种安全的选择,例如,如果你使用 mergeMap 和 interval,并忘记正确处理内部订阅。记住,switchMap 同一时间只维护一个内部订阅,在示例 1中可以清楚出看到这一点。

    不过要小心,在每个请求都需要完成的情况下,考虑写数据库,你可能要避免使用 switchMap 。如果源 observable 发出速度足够快的话,switchMap 可以取消请求。在这些场景中,mergeMap 是正确的选择。

    switchMap - 图4

    示例

    示例 1: 每5秒重新启动 interval

    ( StackBlitz |
    jsBin |
    jsFiddle )

    1. import { timer } from 'rxjs/observable/timer';
    2. import { interval } from 'rxjs/observable/interval';
    3. import { switchMap } from 'rxjs/operators';
    4. // 立即发出值, 然后每5秒发出值
    5. const source = timer(0, 5000);
    6. // 当 source 发出值时切换到新的内部 observable,发出新的内部 observable 所发出的值
    7. const example = source.pipe(switchMap(() => interval(500)));
    8. // 输出: 0,1,2,3,4,5,6,7,8,9...0,1,2,3,4,5,6,7,8
    9. const subscribe = example.subscribe(val => console.log(val));
    示例 2: 每次点击时重置

    ( StackBlitz |
    jsBin |
    jsFiddle )

    1. import { fromEvent } from 'rxjs/observable/fromEvent';
    2. import { interval } from 'rxjs/observable/interval';
    3. import { switchMap, mapTo } from 'rxjs/operators';
    4. // 发出每次点击
    5. const source = fromEvent(document, 'click');
    6. // 如果3秒内发生了另一次点击,则消息不会被发出
    7. const example = source.pipe(
    8. switchMap(val => interval(3000).pipe(mapTo('Hello, I made it!')))
    9. );
    10. // (点击)...3s...'Hello I made it!'...(点击)...2s(点击)...
    11. const subscribe = example.subscribe(val => console.log(val));
    示例 3: 使用 resultSelector 函数

    ( StackBlitz |
    jsBin |
    jsFiddle )

    1. import { timer } from 'rxjs/observable/timer';
    2. import { interval } from 'rxjs/observable/interval';
    3. import { switchMap } from 'rxjs/operators';
    4. // 立即发出值, 然后每5秒发出值
    5. const source = timer(0, 5000);
    6. // 当 source 发出值时切换到新的内部 observable,调用投射函数并发出值
    7. const example = source.pipe(
    8. switchMap(
    9. _ => interval(2000),
    10. (outerValue, innerValue, outerIndex, innerIndex) => ({
    11. outerValue,
    12. innerValue,
    13. outerIndex,
    14. innerIndex
    15. })
    16. )
    17. );
    18. /*
    19. 输出:
    20. {outerValue: 0, innerValue: 0, outerIndex: 0, innerIndex: 0}
    21. {outerValue: 0, innerValue: 1, outerIndex: 0, innerIndex: 1}
    22. {outerValue: 1, innerValue: 0, outerIndex: 1, innerIndex: 0}
    23. {outerValue: 1, innerValue: 1, outerIndex: 1, innerIndex: 1}
    24. */
    25. const subscribe = example.subscribe(val => console.log(val));
    示例 4: 使用 switchMap 的倒计时定时器

    ( StackBlitz |
    jsBin |
    jsFiddle )

    1. import { interval } from 'rxjs/observable/interval';
    2. import { fromEvent } from 'rxjs/observable/fromEvent';
    3. import { merge } from 'rxjs/observable/merge';
    4. import { empty } from 'rxjs/observable/empty';
    5. import { switchMap, scan, takeWhile, startWith, mapTo } from 'rxjs/operators';
    6. const countdownSeconds = 10;
    7. const setHTML = id => val => (document.getElementById(id).innerHTML = val);
    8. const pauseButton = document.getElementById('pause');
    9. const resumeButton = document.getElementById('resume');
    10. const interval$ = interval(1000).pipe(mapTo(-1));
    11. const pause$ = fromEvent(pauseButton, 'click').pipe(mapTo(false));
    12. const resume$ = fromEvent(resumeButton, 'click').pipe(mapTo(true));
    13. const timer$ = merge(pause$, resume$)
    14. .pipe(
    15. startWith(interval$),
    16. switchMap(val => (val ? interval$ : empty())),
    17. scan((acc, curr) => (curr ? curr + acc : acc), countdownSeconds),
    18. takeWhile(v => v >= 0)
    19. )
    20. .subscribe(setHTML('remaining'));
    HTML
    1. <h4>
    2. Time remaining: <span id="remaining"></span>
    3. </h4>
    4. <button id="pause">
    5. Pause Timer
    6. </button>
    7. <button id="resume">
    8. Resume Timer
    9. </button>

    相关食谱

    • 智能计数器
    • 进度条

    其他资源

    • switchMap :newspaper: - 官方文档
    • 使用 switchMap 开启流 :video_camera: :dollar: - John Linquist
    • 使用 RxJS 的 switchMap 操作符来映射并打平高阶 observables :video_camera: :dollar: - André Staltz
    • 在 RxJS 中,使用 switchMap 作为打平 observables 的安全默认操作符 :video_camera: :dollar: - André Staltz

    :file_folder: 源码: https://github.com/ReactiveX/rxjs/blob/master/src/internal/operators/switchMap.ts