Why is finalize lifted in RxJS? - rxjs

I am having trouble understanding the finalize operator in RxJS. Let me demonstrate this on an example:
of(null).pipe(
tap({ complete: () => console.log('tap 1 completes') }),
finalize(() => console.log('finalize')),
tap({ complete: () => console.log('tap 2 completes') })
).subscribe({ complete: () => console.log('subscribe completes') });
I would expect the finalize callback to be executed before the second tap. That's not happening, though. Rather the above code produces the following output:
tap 1 completes
tap 2 completes
subscribe completes
finalize
Looking at the implementation I believe the operator is passed (lifted) through the whole observable chain to always be applied at its end. So now I end up with two questions:
What's the rationale behind this design decision? Can you give some explanation on why this is a desireable / advantageous property?
Is there a different operator or other solution to execute code on complete and on error, but in order (i.e. before the second tap in the above example) rather than at the end of the observable chain?

It's important to be aware that finalize() and tap() work very differently. tap() is triggered by next, error and complete notifications while finalize() is only triggerd on chain ubsubscription. In other words finalize() is very similar to using:
const subscription = $source.subscribe();
// This will be always triggered after all `tap()`s
subscription.add(() => console.log('same as finalize()'));
So you can't make finalize() to be invoked before tap(). Also, be aware that finalize() is invoked also when you manually unsubscribe liek the following:
subscription.unsubscribe(); // will always invoke `finalize()` but never `tap()`
One possible solution could be implementing you own finalize() variant that knows the reason why it's being called: https://github.com/martinsik/rxjs-extra/blob/master/doc/finalizeWithReason.md (see source code)
Also note, that https://github.com/ReactiveX/rxjs/pull/5433 will have no affect on your use-case.

That's the whole principle of the finalize operator. To emit only -after- the source observable completes. Which does mean, after all the complete subscriptions have been handled, counting the tap complete. From the docs:
Returns an Observable that mirrors the source Observable, but will call a specified function when the source terminates on complete or error.
Now you could place the finalize in an inner observable, but I suppose you wouldn't like the order then either
of(null).pipe(
tap({ complete: () => console.log('tap 1 completes') }),
concatMap((resp) => of(resp).pipe(
finalize(() => console.log('finalize'))
)),
tap({ complete: () => console.log('tap 2 completes') })
).subscribe({ complete: () => console.log('subscribe completes') });
This will make the finalize execute before the first and last tap, this is because of the complete object you pass into tap. If you just pass in a function in the first tap, it will have the right order.
Another way could be the usage of concat:
concat(
of(null).pipe(
tap({complete: () => console.log('tap 1 completes' ) }),
finalize(() => console.log('finalize'))
),
EMPTY.pipe(
tap({complete: () => console.log('tap 2 completes' ) })
)
).subscribe({ complete: () => console.log('subscribe completes') });
But this kinda prevents you from accessing what ever the first observable emitted. So, basically, I don't think there is a proper solution to what you are asking :)

Related

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

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.

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));

Determine what caused an observable to complete

I have the following basic Observable setup:
fooObservable
.pipe(takeUntil(barObservable))
.subscribe({
complete: () => {
// Do something only if fooObservable completes, not if barObservable emits.
}
})
As you can see in the comment above, I want to "do something" in the completion block only if the observable is completed due to fooObservable completing. If it completes due to barObservable emitting, I do not want to do anything. How can I determine what caused this observable to complete to write this logic?
There may be a way to address your question using the tap operator just before the takeUntil operator and passing into the complete function of the tap operator the logic you want to run when foo completes.
This is the code and this is a stackblitz
const foo = of('foo').pipe(
delay(200)
)
const bar = of('bar').pipe(
delay(300)
)
foo
.pipe(
tap({
complete: () => console.log('DONE because FOO completed')
}),
takeUntil(bar)
)
.subscribe({
complete: () => console.log('don not do anything since you are here because BAR emitted')
})
If your requisite requires strictly that you run the code you want to run in the complete function passed to the subscribe method, then you can do something similar but you would be required to use some variable to store the state. Such variable should be set withing the complete function of the tap operator
The first idea I got:
let barEmitted = false;
fooObservable
.pipe(takeUntil(barObservable.pipe(tap(() => barEmitted = true))))
.subscribe({
complete: () => {
if(!barEmitted) { do your stuff}
}
})
a bit hacky, but I've never encountered your situation before :)
If fooObservable terminates because of barObservable and you want to execute some logic when barObservable emits the first value you can move this logic to barObservable :
barObservable
.pipe(take(1))
.subscribe(e => /*Do something only if barObservable completes*/)
fooObservable
.pipe(takeUntil(barObservable))
.subscribe({
complete: () => {
// continue to do nothing
}
})

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.

Proper way to complete an rxjs observable interval?

My scenario is that I add a record set to a host zone via aws sdk. When adding a record set, the aws sdk has a GetChange call that can be used to get that status. Here is the code I am currently doing:
this._adminService.registerDomain(caseWebsiteUrl.Url).
subscribe(id => {
return Observable.interval(5000).flatMap(() => {
return this._adminService.getChange(id);
}).
takeWhile((s) => s.ChangeInfo.Status.Value !== 'INSYNC').subscribe(
() => {
},
() => {
},
() => this.urlStatus = 'fa fa-check');
});
In the above code, I want to call registerDomain and after that has been successful, I want to call getChange every 5 seconds until the Status.Value !== 'INSYNC'
A few questions:
What is flatMap doing?
Is it possible to do this without 2 subscribe calls?
If I don't need the next or error callbacks, but I need the complete, is it necessary to declare empty bodies?
Flatmap aka MergeMap will flatten higher order observables. Thus Observable<Observable<T>> => Observable<T>.
The subscribe inside subscribe is a code smell and can and should be refactored. If you do not need the error/complete handlers you do not need to pass those. For instance:
function registerDomain(caseWebsiteUrl) {
return this._adminService.registerDomain(caseWebsiteUrl.Url)
.concatMap(registerId => Observable.interval(5000)
.mergeMap(() => this._adminService.getChange(registerId))
.takeWhile((info) => info.ChangeInfo.Status.Value !== 'INSYNC')
)
}
registerDomain.subscribe(res => console.log('res:'+res));
This works based on the assumption and limitations that:
registerDomain() returns an Observable which completes
getChange() will eventually return 'INSYNC'
No error handling has been added (for instance a timeout after 30 seconds? Retry if registerDomain() fails?)

Resources