Home > database >  Using forkjoin, but without the supplied observables completing
Using forkjoin, but without the supplied observables completing

Time:10-12

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);
);`
  • Related