i have a tricky one today.
I have been dabbling with RXJS and observables over the past week, and have come upon a conundrum: Forkjoin()
I like how it waits until all given observables have emitted a value, and then continues to do its thing. but what i don't like, is that it needs the given observables to actually complete.
This makes it hard to use for the like of buttons that needs to be able to be re-clicked (and do mulitple requests for example)
I would use CombineLatest() but that will have the two streams deliver the request independently from each other(one could arrive a lot sooner than the other which is unwanted behavior).
So my question is:
Is it possible to fire off forkjoin() without the underlying observables completing?
If not, is it possible to somehow re-use a completed observable?
I will now supply the code:
import {catchError, exhaustMap, forkJoin, fromEvent, of, take, tap} from 'rxjs';
import {addImages} from './helpers';
import {ajax} from "rxjs/ajax";
import {map} from "rxjs/operators";
import {switchMap} from "rxjs";
const observer = {
next: val => addImages({dog: val.dog, cat: val.cat}),
error: err => (console.log('error handled in observer')),
complete: () => console.log('complete!')
};
const fetchButton = document.querySelector('.fetchImages');
const dogSource$ = fromEvent(fetchButton, 'click');
const catSource$ = fromEvent(fetchButton, 'click');
const dogImages$ = dogSource$.pipe(
exhaustMap(() => ajax.getJSON('http://localhost:3000/dog'))).pipe(catchError(error => {
console.log('error, handling DOG error locally');
return of({message: 'https://world.openfoodfacts.org/images/products/899/110/278/9318/front_en.9.full.jpg'})
})
).pipe(
map(item => item.message),
take(1),
tap(console.log)
)
const catImages$ = catSource$.pipe(
exhaustMap(() => ajax.getJSON('https://some-random-api.ml/img/cat'))).pipe(
catchError(error => {
console.log('error, handling CAT error locally')
return of({link: 'https://world.openfoodfacts.org/images/products/930/065/703/2029/front_en.3.full.jpg'})
})
).pipe(
map(item => item.link),
take(1),
tap(console.log)
)
forkJoin({
dog: dogImages$,
cat: catImages$
}).pipe(
tap(console.log)
).subscribe(observer);
function addImages({dog, cat}) {
document.querySelector('[alt="dog"]').setAttribute('src', dog);
document.querySelector('[alt="cat"]').setAttribute('src', cat);
}
<section>
<button >Fetch!</button>
<div >
<img alt="dog">
<img alt="cat">
</div>
</section>
CodePudding user response:
I think zip
is exactly what you're after: take a pair (or n) values from two (or n) streams, emit when you have both (all), and keep going. Do sth like this:
const observer = {
next: ([cat, dog]) => addImages({ cat, dog }),
error: err => (console.log('error handled in observer')),
complete: () => console.log('complete!')
};
// ...
zip(catImages$, dogImages$)
.pipe(tap(console.log))
).subscribe(observer);
CodePudding user response:
I think in your case the you should use zip
or combineLatestWith
or in this case maybe you can use pairwise
but I don't think it's the right one
CodePudding user response:
Found a solution (but open to hear critique if there could be unwanted side effects).
I ended up adding repeat() in the pipe after forkjoin.
This will re-subscribe the observable upon completion.
CodePudding user response:
Hope it helps you
`forkJoin([dogImages$, catImages$]).subscribe(data => {
const [dog, cat] = data;
console.log(dog);
console.log(cat);
);`