2

The following use of switchMap...

Rx.Observable.timer(0, 1000)

  // First switchMap
  .switchMap(i => {
    if (i % 2 === 0) {
      console.log(i, 'is even, continue');
      return Rx.Observable.of(i);
    }
    console.log(i, 'is odd, cancel');
    return Rx.Observable.empty();
  })

  // Second switchMap
  .switchMap(i => Rx.Observable.timer(0, 500).mapTo(i))

  .subscribe(i => console.log(i));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.min.js"></script>

... produces the following output:

0 is even, continue
0
0
1 is odd, cancel
0 // <-------------------- why?
0 // <-------------------- why?
2 is even, continue
2
2
3 is odd, cancel
2 // <-------------------- why?
2 // <-------------------- why?
4 is even, continue
.
.
.

I would expect the following output:

0 is even, continue
0
0
1 is odd, cancel
2 is even, continue
2
2
3 is odd, cancel
4 is even, continue
.
.
.

I expected that the first switchMap would cancel everything downstream, every time it returns, which I guess was incorrect.

Is there a way to achieve the desired behavior?

marius
  • 1,533
  • 3
  • 16
  • 22

1 Answers1

2

The reason is that returning an empty observable does not cause it switch to the new observable, that is the second switchMap never gets called.

A really hacky solution would be to return a magic value that you can ignore later

const CANCEL = {};
Rx.Observable.timer(0, 1000)

  // First switchMap
  .switchMap(i => {
    if (i % 2 === 0) {
      console.log(i, 'is even, continue');
      return Rx.Observable.of(i);
    }
    console.log(i, 'is odd, send CANCEL observable');
    return Rx.Observable.of(CANCEL);
  })


  // Second switchMap
  .switchMap(i => Rx.Observable.timer(0, 500).mapTo(i))

  // Filter out cancelled events
  .filter(i => i != CANCEL)
  
  .subscribe(i => console.log(i));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.min.js"></script>
Ruan Mendes
  • 90,375
  • 31
  • 153
  • 217
  • 1
    So that works, but for my use case it's a bit inconvenient -- I have a sequence of maybe 8 switchMaps, so all of these would have to check for that case. I'm wondering if there's another operator that I should be using instead... – marius Mar 16 '18 at 17:13
  • I accepted it, because in the end I also think that's probably the only way. I'll have a look and will try to see if maybe my approach with switchMap to this problem is wrong. Thanks! – marius Mar 16 '18 at 17:24
  • 1
    I know, it's not great, but the point of switch map is that it cancels when a new observable is emitted. In your case, you don't want to emit a new observable, so it won't cancel it. I think you need to implement a custom operator doesn't filter out empty observables. – Ruan Mendes Mar 17 '18 at 18:50