Can I remove .subscribe() from map by using another RxJS operator? - rxjs

Is there an alternative to doing .subscribe() in map?
return this.http.patch<any>(URL, { test: true }).pipe(
tap(_ => this.nextSomething$.next({})),
filter(_ => this.filter),
map(resp => {
this.someObservable({ message: 'do' }).subscribe()
return resp
})
)
I tried doing switchMap and returning the previous response but my observable does not complete.
switchMap(prevResp =>
this.someObservable({ message: 'do' }) }).pipe(map( _ => prevResp))
)
Thank you

If your observable is not completing when you switch to using the flattening operator then that means the target observable in that operator isn't completing. So you might want to check what's going on in someObservable that causes it to never complete. It is likey that behavior isn't desirable.
If you know that someObservable will emit at least once, then you can add the first operator to the inner pipe method:
switchMap(prevResp =>
this.someObservable({ message: 'do' }) }).pipe(
first(),
map( _ => prevResp)
)
)
If you don't care what someObservable does - you don't want to wait for it just want it to execute, then wrap the observable in firstValueFrom. It converts an observable into a promise that emits the first result. This code smells, but it should do the trick.
this.http.patch<any>(URL, { test: true }).pipe(
tap(() => this.nextSomething$.next({})),
filter(() => this.filter),
tap(() => firstValueFrom(this.someObservable({ message: 'do' })))
)
Maybe, you don't care to wait for this observable to emit, but you're still looking for an orderly execution of someObservable. In that case you can use a subject that emits every time you want it called, and use concatMap to ensure the execution is performed in an orderly fashion.
private readonly someObservableRequest = new Subject<string>();
constructor() {
this.someObservableRequest.pipe(
concatMap((message) => this.someObservable({ message }))
).subscribe(); // be kind, please unsubscribe
}
someMethod() {
return this.http.patch<any>(URL, { test: true }).pipe(
tap(_ => this.nextSomething$.next({})),
filter(_ => this.filter),
tap(() => this.someObservableRequest('do'))
);
}

If I understand your point right, you have to execute consecutively 2 Observables.
In this case you need yo use one so called "higher order" operators, i.e. either concatMap or switchMap (there are other "higher order" operators but I feel they do not apply to your case).
The code then would look like this
myNewOservable this.http.patch<any>(URL, { test: true }).pipe(
tap(_ => this.nextSomething$.next({})),
filter(_ => this.filter),
// an higher order operator returns an Observable
concatMap(resp => {
return this.someObservable({ message: 'do' })
})
)
return myNewOservable
Now you can subscribe to myNewOservable.
In my example I have used concatMap which ensures that a value notified by the upstream Observable is processed through the downstream pipeline before processing the next value from upstream.
I could have used also switchMap, which has a slightly different behavior: as soon as a new value is notified by the upstream Observable, any downstream processing is terminated.
In this case, given that http emits only one value and then completes the 2 operators act the same.

Related

Why is a stopped BehaviorSubject halting execution in a pipe of RXJS?

I have the following code:
this.workingStore$.pipe(
filter((workingStores) => !!workingStores[docID]),
concatMap((workingStores) => {
console.log(
'returning from concatMap',
workingStores[docID].getInitialDataSet(),
);
return workingStores[docID].getInitialDataSet();
}),
filter((isSet) => {
console.log('looking for set', isSet);
return isSet;
}),
),
workingStores[docID].getInitialDataSet() returns an Observable. Because the pipes that set it to true complete, the BehaviorSubject gets isStopped: true internally. Once it becomes true, the filter no longer fires for isSet.
Shouldn't it just know to return the final value? It seems that's not the case so how would I wrote this so the last filter always runs? If I do the following, it works, but is awfully code smelly
concatMap((workingStores) => {
if (
workingStores[docID].getInitialDataSet().getValue() === true
) {
return of(true);
}
return workingStores[docID].getInitialDataSet();
}),
I am aware ReplaySubject will give values, even after stopped, but I don't want to emit old values to any subscriber.
ReplaySubject has a constructor that accepts the number of latest events to replay. If you provide 1 it will act similarly to your BehaviorSubject.

How can i execute asynchronous code when an RxJS observable complete?

I would like to execute code when the observable complete. In my code, i execute this:
compact(): Observable<FileManifest> {
return this.loadIndex().pipe(
mergeMap((index) => index.walk()),
map((entry) => entry.manifest),
notUndefined(),
writeAllMessages(this.newPath, ProtoFileManifest),
finalize(async () => {
await Promise.all([
promises.rm(this.journalPath, { force: true }),
promises.rm(this.manifestPath, { force: true }),
]);
await promises.rename(this.newPath, this.manifestPath);
}),
);
}
The problem is that the finalize method is made for synchronous code. When i execute asynchronous code like above, the code will be executed independently from the subscribe.
I would like this will be execute when disposing resource of the observable but i want that when i subscribe, i always receive the event.
How can i put asynchronous code in the finalize method ?
Thanks
Ulrich
One way to do it is to create three observables instead of trying to do it all
in one. Each will make up a link in the sequential async chain you want to
make.
In order for the side effects in the promise-based observables to be lazy, we use defer.
Note that the defer callback's return value can be an observable, or an
"ObservableInput", which is what RxJS calls values it knows how to turn
into observables. This value can be (among other things) a promise.
({
compact(): Observable<FileManifest> {
const writeToTempManifest$ = this.loadIndex().pipe(
mergeMap((index) => index.walk()),
map((entry) => entry.manifest),
notUndefined(),
writeAllMessages(this.newPath, ProtoFileManifest)
);
const removeOldManifest$ = defer(() =>
Promise.all([
promises.rm(this.journalPath, { force: true }),
promises.rm(this.manifestPath, { force: true }),
])
);
const renameNewManifest$ = defer(() =>
promises.rename(this.newPath, this.manifestPath)
);
return from([
writeToTempManifest$,
removeOldManifest$,
renameNewManifest$,
]).pipe(concatAll());
},
});
Note that each of these observables potentially emits something (though I'm not familiar with the API). The first emits whatever the writeAllMessages operator does, while the second and third emit the resolved values of their respective promises. In the case of the second one, that's a two element array from the Promise.all.
If you want to suppress an observable's emitted values while still keeping it open until it completes, you can create an operator that does just that:
const silence = pipe(concatMapTo(EMPTY));

WithLatestFrom($foo) not emitting when $foo emits, possibly because of the use of merge

I am implementing an observable which can be subscribed to before it is "assigned" Think of it like hoisting an observable definition so I dont have to worry about the order in which I create observables derived from other observables, I call it a ColdSubject.
ColdSubject works fine (I can add observables to it, and only when somebody subscribes to the ColdObservable do its operators get evaluated).
However withLatestFrom will never emit while waiting for obs$, despite the observable it's "waiting for" emitting to a subscriber several times!
export class ColdSubject<T> {
// If you subscribe to this before an observable has been added to $acc, you will be notified as soon as one is added, and if you subscribe to this after an observable is added to acc$ you will also be notified
public obs$: Observable<T>;
public acc$ = new BehaviorSubject<Observable<T>>(merge());
constructor() {
this.obs$ = this.acc$.pipe(switchMap(v => v));
}
addObservable(newObservable: Observable<T>) {
this.acc$.next(merge(this.acc$.getValue(), newObservable))
}
}
const foo = new ColdSubject<number>();
# I know this observable is waiting for withLatestFrom because "Tap yeet" is logged
of('yeet').pipe(
tap(v => console.log(`tap ${v}`)),
withLatestFrom(foo.obs$)
).subscribe(v => {
console.log(`WithLatestFrom ${v}`);
});
# This observable will begin emitting 5 seconds into the script, because I wait 5 seconds to subscribe to it
foo.addObservable(
interval(1000).pipe(
take(5),
tap(v => console.log(`Interval ${v}`))
)
);
# Subscribe 5 seconds into script start, so I know that my ColdSubject only evaluates its observables once they're subscribed to
setTimeout(
() => foo.obs$.subscribe(v => console.log(`Subscribe ${v}`)),
5000
);
Why does foo.obs$ emit several times, while the operation waiting for its latest value not emit?
Looking at the source code one can see that withLatestFrom is triggered by_next which is fired by the source Observable calling next:
protected _next(value: T) {
if (this.toRespond.length === 0) {
/**
* value - emitted by the source Observable
* ...this.values - emitted by the Observables passed to `withLatestFrom`
*/
const args = [value, ...this.values];
if (this.project) {
this._tryProject(args);
} else {
this.destination.next(args);
}
}
}
Your issue is that your source completes right away, while the Observable passed to withLatestFrom has not emitted yet. by the time foo.obs emits, your source Observable has long since completed.
What I would recommend of using in your case is combineLatest as demonstrated below:
combineLatest(of("yeet"), foo.obs$)
.pipe(
tap(v => console.log(`tap ${v}`)),
)
.subscribe(v => {});
of('yeet') emits and is complete so withLatestFrom will complete as the source is complete.
Change your subscription to
of('yeet').pipe(
tap(v => console.log(`tap ${v}`)),
withLatestFrom(foo.obs$)
).subscribe({ complete: () => console.log('yeet complete') });
and you will see it is infact complete.
https://stackblitz.com/edit/rxjs-6-opeartors-2nuam1?file=index.ts

RxJs: finalize for not complete observable

I have the following code:
this.userService.get().pipe(
catchError(() => EMPTY) // Do something here
).subscribe(() => {}) // And here
I want to do some stuff no matter there was an error or not. Just like finalize but for not completed observable. I tried to use tap, but it works only if there's no error. I don't want to duplicate my code and add it to the catchError. What should I do?
there's no other way to do it with catchError without touching it in the current example . Because everything after catchError won't get any notification, it's like an observable that never emits.
you can use 2nd argument of tap, it is triggered on error, the same as in subscribe.
this.userService.get().pipe(
tap(
() => console.log('emit'),
() => console.log('error'),
() => console.log('complete'),
),
// catchError(() => EMPTY), // closing the stream and hiding the error.
// repeat(), // in case if you want to resubscribe once the stream has been closed.
).subscribe(
() => console.log('emit'),
() => console.log('error'),
() => console.log('complete'),
);
Emit a default value in catchError.
this.userService.get().pipe(
catchError(() => of(null)) // catch error and emit null instead (or some other value)
).subscribe(value => {
// do something here,
// value will be null when this.userService.get() errors */
})
You should consider moving your error handling with catchError into the Service.

distinctUntilChanged in nested pipe with switchMap

I have an observable stream set up as below. I have an interval that is polling every two seconds. I then switchMap that to make two dependent API calls (mocked here with 'of's). After, I want to use distinctUntilChanged to make sure the final object is different. The only thing is that distinctUntilChanged doesn't fire.
I'm assuming it has SOMETHING to do with the fact that we are creating new streams and therefore never collects two objects to compare, but I don't fully understand.
interval(2000).pipe(
switchMap(() => loadData()),
)
.subscribe(res => console.log(res)); // { name: 'test' } is printed every two seconds
function loadData() {
return of('API call').pipe(
mergeMap(numb => of({ name: 'test' })),
distinctUntilChanged((prev, cur) => {
console.log('CompareFn'); // This will never fire.
return JSON.stringify(prev) === JSON.stringify(cur)})
);
}
Stackblitz: https://stackblitz.com/edit/rxjs-ko6k3c?devtoolsheight=60
In this case, I would like there to only be a single value ever printed from the next handler as distinctUntilChanged should stop all values after the first.
Would appreciate an explanation as to why this isn't working as I would expect it to.
the problem is that your distinctUntilChanged is operating on the inner observable, not the outter... you need to do this
interval(2000).pipe(
switchMap(_ => loadData()),
distinctUntilChanged((prev, cur) => {
console.log('CompareFn');
return JSON.stringify(prev) === JSON.stringify(cur);
})
)
.subscribe(res => console.log(res));
function loadData() {
return of('API call').pipe(
mergeMap(numb => of({ name: 'test' }))
);
}
in your prior set up, only one value ever reached distinctUntilChanged as the interval switched into a new observable via switch map.

Resources