I have a stream that is by default an empty object. Over time this object gets its keys filled.
const RXSubject = new BehaviorSubject({});
RXSubject.pipe(
filter((frame): frame is InstDecodedFrame => frame.type === FrameType.INST),
scan<InstDecodedFrame, InstantDataDictionnary>(
(acc, frame) => ({ ...acc, ...frame.dataList }),
{},
),
);
Now I subscribe to the filter on some part of the app, but if I subscribe somewhere else and the last value has not been triggering the filter condition. My new observable just get nothing.
Is there any way that I get the latest "valid" value from the pipe in any of subscriber to the pipe ?
Thanks
You can use shareReplay(1) after filter() and subscribe to that observable:
const obs$ = RXSubject.pipe(
filter((frame): frame is InstDecodedFrame => frame.type === FrameType.INST),
scan<InstDecodedFrame, InstantDataDictionnary>(
(acc, frame) => ({ ...acc, ...frame.dataList }),
{},
),
shareReplay(1),
);
Then you'll subscribe to obs$ instead of RXSubject.
Related
I try to use rxjs rety operator, its work fine with observable:
const obs$: Observable<number> = new Observable((observer) => {
observer.next(1);
observer.complete();
});
obs$
.pipe(
mergeMap(async () => [
await dataService.getAllSomthing(),
await dataService.getAllomthingElse(),
await dataService.getAllSomthingElseElse(),
]),
map(([somthing, somthingElse, somthingElseElse]) => {
dispatch(
enableToasterAction({
text: "good",
type: ToasterType.Success,
})
);
}),
retry(2),
catchError((err) => {
return of(null);
})
)
.subscribe((val: any) => {});
But its not work when I using Subject:
const sub=new Subject();
sub
.pipe(
switchMap(async ({ somthinng}: any) => {
return [await dataService.getSomthing(somthinng)];
}),
map(([somthinngRes]) => {
dispatch(
enableToasterAction({
text: "good",
type: ToasterType.Success,
})
);
}),
retry(2),
catchError((err) => {
return of(null);
})
)
.subscribe((val: any) => {});
sub.next({});
Someone can help me to understand what the difference between them, why its work with observable, but not with subject ?
You can retry an cold observable but not hot observable (subject)
if you want to retry a action trigger by hot observable, you can however move the retry() operator to inner observable. For example
fromEvent(document,'click').pipe(
switchMap(evt=>from(fetch('someurl')).pipe(retry(2))
)
That way the http call triggered by click will retry 2 times when it fails
Subject has an internal state and once it receives complete or error notification it marks itself as isStopped and will never ever emit anything.
So retry() tries to resubscribe but the source Subject will just return Subscription.EMPTY and won't make a real subscribtion.
How to pause observable every 15 seconds then wait for 5s and then continue emitting?
I made this example:
const digits = interval(1000);
const pauser$ = interval(20000).pipe(mapTo(true));
pauser$.subscribe(item =>
console.log(`${new Date().toISOString()} pause fired`)
);
const pauseStopper$ = pauser$.pipe(
concatMap(_ => timer(5000)),
tap(() => console.log(`${new Date().toISOString()} pause stopped`)),
mapTo(false)
);
const observable = merge(pauser$, pauseStopper$).pipe(
startWith(false),
switchMap(paused => (paused ? NEVER : digits))
);
observable.subscribe(
item => console.log(`${new Date().toISOString()}: ${item}`),
console.error,
() => console.log("complete")
);
After first pause cycle it behaves nearly close to what I need. Any ideas on how to make this more clear?
stackblitz example
I created a player function that gets three params:
source$: Your source observable that should be paused/played
play$: Continues to emit your source$ observable
pause$: Pauses your source$ observable
const player = <T>(source$: Observable<T>, play$: Observable<void>, pause$: Observable<void>): Observable<T> =>
merge(
play$.pipe(
switchMap(() => of(source$))
),
pause$.pipe(
switchMap(() => of(NEVER))
)
).pipe(
switchMap(stream => stream)
)
This function currently throws an error as far as I can see. In the short time I have atm I could not fix the error. The error is caused by of(void 0) in the pause$ switchMap. Later I will try to fix this error. Forfeit the error the function works.
You can now use a play$ and pause$ Observable to emit the play/pause:
const play$: Observable<void> = interval(20000).pipe(
startWith(void 0),
mapTo(void 0)
);
const pause$: Observable<void> = play$.pipe(
switchMap(() => interval(15000).pipe(
take(1)
)),
mapTo(void 0)
);
const player$ = player(source$, play$, pause$);
Does this solve your problem? If there are any issues with the solution let me know and I try to adapt.
I'm trying to make multiple http requests and get returned data in one object.
const pagesToFetch = [2,3]
const request$ = forkJoin(
from(pagesToFetch)
.pipe(
mergeMap(page => this.mockRemoteData(page)),
)
)
mockRemoteData() return a simple Promise.
After first Observable emits (the once created from first entry of pagesToFetch the request$ is completed, second value in not included. How can I fix this?
You can turn each value in pagesToFetch into an Observable and then wait until all of them complete:
const observables = pagesToFetch.map(page => this.mockRemoteData(page));
forkJoin(observables)
.subscribe(...);
Or in case it's not that simple and you need pagesToFetch to be an Observable to collect urls first you could use for example this:
from(pagesToFetch)
.pipe(
toArray(),
mergeMap(pages => {
const observables = pages.map(page => this.mockRemoteData(page));
return forkJoin(observables);
}),
)
.subscribe(...);
Try the below sample format...
Observable.forkJoin(
URL 1,
URL 2
).subscribe((responses) => {
console.log(responses[0]);
console.log(responses[1]);
},
error => {console.log(error)}
);
I have the following searchService.search method that returns a forkJoin of two api calls.
I want the calls to execute simultaneously which they are but I also want each response back as a single object that can be passed into my SearchSuccess action and processed immediately without waiting for all calls to complete. Currently they are returning as an array of responses and only upon completion of both API calls - as this is what forkJoin is used for.
My issue is that I'm struggling to find another operator that does what I want.
Or perhaps the code pattern requires some redesign?
action:
#Effect()
trySearch: Observable<Action> = this.actions$.pipe(
ofType(SearchActionTypes.TrySearch),
switchMap((action: TrySearch) =>
this.searchService.search(action.payload)
.pipe(
map((data) => new SearchSuccess(data)),
catchError(error => of(new SearchFail(error))),
),
),
);
SearchService (snippet):
search(searchForm: SearchForm): Observable<any> {
const returnArray = [];
if (searchForm.searchClients) {
const searchClientParams = new Search();
searchClientParams.searchPhrase = searchForm.searchPhrase;
searchClientParams.type = SearchType.Client;
const searchClients = this.objectSearch(searchClientParams);
returnArray.push(searchClients);
}
if (searchForm.searchContacts) {
const searchContactParams = new Search();
searchContactParams.searchPhrase = searchForm.searchPhrase;
searchContactParams.type = SearchType.Contact;
const searchContacts = this.objectSearch(searchContactParams);
returnArray.push(searchContacts);
}
return Observable.forkJoin(returnArray);
}
If I understand it correctly returnArray contains two Observables and you want to wait until they both complete but still you want to emit each result separately.
Since forkJoin emits all results in a array you could just unwrap it with mergeMap (or concatMap):
this.searchService.search(action.payload)
.pipe(
mergeMap(results => results),
map((data) => new SearchSuccess(data)),
catchError(error => of(new SearchFail(error))),
),
Might be that I'm a noob and not fully understanding how this stuff should work yet, but I have an epic in redux-observable in which I want to use as a way to create a promise which will dispatch an action and wait for a different action before resolving. I've got it working by mapping the action to '__IGNORE__' but I really don't want to do that. Is there any way to just have an epic handle an action, but not pass anything else on?
Here's my code:
export const waitFor = (type, action) => new Promise((resolve, reject) => {
const waitForResult = action$ => action$.ofType(type).do(() => resolve()).mapTo({type: "___IGNORE___"});
registerEpic(waitForResult);
action();
});
You can throw away any next'd values from an observable chain by using the .ignoreElements() RxJS operator
action$.ofType(type)
.do(() => resolve())
.ignoreElements();
Another way of doing this (no more right or wrong) is to create an anonymous Observable that just subscribes.
const waitForResultEpic = action$ => new Observable(observer =>
action$.ofType(type)
.subscribe(() => resolve())
);
This is implicitly returning the subscription we create, so that it's attached to the lifecycle of our rootEpic as well. Because we never call observer.next(), this epic never emits any values; just like ignoreElements().
Although you didn't ask, you may eventually notice that your epic will run forever, listening for that incoming action matching the type variable. This may not be not what you want, if you want to match once then complete.
You can accomplish that using .take(1) operator.
const waitForResult = action$ =>
action$.ofType(type)
.take(1)
.do(() => resolve())
.ignoreElements();
Or
const waitForResult = action$ => new Observable(observer =>
action$.ofType(type)
.take(1)
.subscribe({
next: () => resolve(),
error: err => observer.error(err),
complete: () => observer.complete()
})
);
This will only match once per the life of the application--once received it will never do it again.