Reputation: 4345
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?
Upvotes: 3
Views: 8194
Reputation: 58400
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.
Upvotes: 4