I think I've found a bug in the RxJS library or just can't understand it's behavior. Take a look at the following code:
const { Observable, Subject } = Rx;
const source$ = new Subject();
const handle = v => {
if (v === 0) {
source$.next(1);
return Observable.empty();
}
return Observable.interval(1000).startWith(-1).take(3);
}
source$
.do(e => console.log('source:', e))
// .delay(1)
// .switchMap(v => Observable.of(null).switchMap(() => handle(v)))
.switchMap(v => handle(v))
.subscribe(e => console.log('handle:', e))
source$.next(0); // <- initial value
<script src="https://unpkg.com/rxjs/bundles/Rx.min.js"></script>
Working scenario: When I run it with source$.next(1), I see the following result(which is correct):
source: 1
handle: -1
handle: 0
handle: 1
Broken scenario: When I run it with source$.next(0), I expect to see:
source: 0
... (exactly the same output as for the previous example)
But instead I see only 3 values:
source: 0
source: 1
handle: -1
Workaround 1: When I put .delay(1) as you can see in the comments - it works fine, just as I have expected. I think this is because default Scheduler executed synchronously in the same tick vs using async Scheduler when using delay.
Question
When I use Observable.of(null) (see comments in the code) it gives me the same("correct" or expected) result too. Why is that? Shouldn't it be the same synchronous as before?
The behaviour is correct. It's the synchronous emission from the source that makes it a little weird.
Emitting 1 from within the handle method effects a re-enterant call that sees the startWith value of -1 emitted before the Observable.empty is returned - as the startWith value is emitted synchronously.
That means that the switchMap sees the startWith/interval observable first and the empty observable second. It then switches to the empty observable, effecting the output that you see.
Adding a delay prevents the re-enterant call.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With