Reputation: 2217
I have an unexpected behavior with the operator withLatestFrom.
Output
map a
a
map a <== why a is mapped again ?
map b
b
const { Subject, operators } = window.rxjs
const { map, withLatestFrom } = operators
const createA = new Subject()
const createB = new Subject()
const a = createA.pipe(
map(() => console.log('map a'))
)
const b = createB.pipe(
withLatestFrom(a),
map(() => console.log('map b'))
)
a.subscribe(() => { console.log('a') })
b.subscribe(() => { console.log('b') })
createA.next()
createB.next()
<script src="https://unpkg.com/@reactivex/[email protected]/dist/global/rxjs.umd.js"></script>
Upvotes: 1
Views: 186
Reputation: 8022
The problem here isn't with withLatestFrom()
but rather with how subscriptions work. Observables are lazy and don't run until you subscribe. Each new subscriptions will run the observable again.
const stream$ = from([1,2,3]);
stream$.subscribe(console.log) // output: 1 2 3
stream$.subscribe(console.log) // output: 1 2 3
In this case, `from([1,2,3)] ran twice. If I alter my stream, anything I do will happen for each subscriber.
const stream$ = from([1,2,3]).pipe(
tap(_ => console.log("hi"))
);
stream$.subscribe(console.log) // output: hi 1 hi 2 hi 3
stream$.subscribe(console.log) // output: hi 1 hi 2 hi 3
The final piece of the puzzle is this: internally withLatestFrom()
subscribes to the stream that you give it. Just like an explicit .subscribe()
runs the observable, so does withLatestFrom()
once it's subscribed to.
You can use shareReplay
to cache the latest values and replay them instead of running the observable again. It's one way to manage a multicasted stream:
const createA = new Subject()
const createB = new Subject()
const a = createA.pipe(
tap(() => console.log('tap a')),
shareReplay(1)
)
const b = createB.pipe(
withLatestFrom(a),
tap(() => console.log('tap b'))
)
a.subscribe(() => { console.log('a') })
b.subscribe(() => { console.log('b') })
createA.next()
createB.next()
Now a.subscribe()
and withLatestFrom(a)
are both getting a buffered value that only gets run when createA.next()
is executed.
As an aside, mapping a value to nothing is bad habit to get into. Consider the following code:
from([1,2,3]).pipe(
map(val => console.log(val))
).subscribe(val => console.log(val));
This will output
1
undefined
2
undefined
3
undefined
because you're actually mapping each value to nothing. tap
on the other hand doesn't change the source observable, so it's a much better tool for debugging and/or side effects that don't alter the stream
from([1,2,3]).pipe(
tap(val => console.log(val))
).subscribe(val => console.log(val));
This will output
1
1
2
2
3
3
Upvotes: 2
Reputation: 2217
I found that the operator share()
allows multiple subscribers.
const a = createA.pipe(
map(() => console.log('map a')),
share()
)
Upvotes: 1