Why does retryWhen completes with its inner Observable?
Imagine that we need to make maximum 3 attempts to fetch data. And, for example sake, our 3rd attempt will definitely work. So I try the following:
// ...
retryWhen(errors$ =>
errors$.pipe(
take(3)
)
)
Which will make 3 attempts, but will immediately complete after 3rd error, together with its inner observable. Even if our 3rd attempt was about to give us results.
An illustration:
Run this example
UPDATED NOTE: Probably, to properly limit retry attempts — we should use switchMap to switch based on index to of(error) or to throwError(error).
For me this behavior was unexpected: as I though inner observable will only trigger a retry. And once it notifies about a retry -- it's job is done, and we don't care if it completes.
So this question is rather theoretical then practical:
What was the reasoning behind this particular behavior? And what are its advantages?
NOTE: Its not an issue, operator behaves as described
Returns an Observable that mirrors the source Observable with the
exception of an error. If the source Observable calls error, this
method will emit the Throwable that caused the error to the Observable
returned from notifier. If that Observable calls complete or error
then this method will call complete or error on the child
subscription. Otherwise this method will resubscribe to the source
Observable.
Related
In my application, I need to initiate a burst of long-running http requests which optimally should be allowed to resolve concurrently. The results must be concatenated into a single observable for evaluation downstream.
Merging these observables using mergeMap allows for this concurrency, but this unfortunately does not guarantee order of the results. ConcatMap guarantees order of the values emitted by the output, but it appears that the mapping operation for a given request is not executed until after its predecessor is completed, limiting concurrency to a single request at once (I'm not certain this is how it works but it would explain the result).
Before I start writing a custom operator, I thought I would ask: what is the best way to merge the results from a batch of requests, each returning an observable, while enabling concurrency and guaranteeing order of the results in the output observable?
As the comment said, the forkJoin / combineLatest (you would see the difference between these two by observing their marble diagram), you can get what you want.
How forkJoin works is, it will subscribe to EVERY observables parameter, wait until ALL observables complete, and then emit the value (order is determined by the order of parameter). https://rxjs.dev/api/index/function/forkJoin
And yes, forkJoin is not a pipeable operator, because it's not. It will return an observable. So you can use it with switchMap/other higher order mapping.
Example
const studentsAndTeachers = school$.pipe(
map(school=> school.id),
switchMap(id => forkJoin(http.post('getStudents', id), http.post('getTeachers, id))
)
studentsAndTeachers will get you a tuple ([]) of students and teachers [[students], [teachers]]
I have not been able to find any normative text to answer this question. I have been using this code pattern (this is in TypeScript in an Angular application):
observeSomethingFun$: Observable<Fun>;
...
async loadsOfFun() {
const fun = await this.observeSomethingFun$.toPromise();
// I now have fun
}
In general, Observables need to be unsubscribed from. This happens automatically when the Angular async pipe is used but what happens in this case? Does toPromise unsubscribe after emitting one value?
If not, how do I unsubscribe manually?
Update:
It turns out #Will Taylor's answer below is correct but my question needs some clarification.
In my case the Observable emits a never-ending stream, unlike for example Angular's HttpClient Observables that complete after emitting one value. So
in my case I would never get past the await statement according to Taylor's answer.
RxJS makes this easy to fix. The correct statement turns out to be:
const fun = await this.observeSomethingFun$.pipe(first()).toPromise();
The RxJS first operator will receive the first value and unsubscribe from the source Observable. it will then send out that value to the toPromise operator
and then complete.
No need to unsubscribe.
Which is convenient, as there is no way to unsubscribe from a promise.
If the Observable completes - the promise will resolve with the last value emitted and all subscribers will automatically be unsubscribed at this point.
If the Observable errors - the promise will reject and all subscribers will automatically be unsubscribed at this point.
However, there are a couple of edge cases with toPromise in which the behavior is not completely clear from the docs.
If the Observable emits one or more values but does not complete or error, the promise will neither resolve or reject. In this case, the promise would hang around in memory, which is worth considering when working with toPromise.
If the Observable completes but does not emit a value, the promise will resolve with undefined.
First of all, thank you for this question and answer, I wrongly assumed toPromise() knew what it was doing in all scenarios and would unsubscribe when that observable completes (even if it is an observable stream)
So I will just say that it doesn't hurt to pipe all of your observables before using .toPromise()
I just went through a big ordeal of stepping through our app for memory leaks and found the above answer by Will to be good. The elaboration on the actual question was exactly the same issue I was running into.
We are stepping through each observable in the app right now and we use either
pipe(take(1)) which is equivalent to pipe(first()).
or we use pipe(takeUntil(this.destroyed)) where this.destroyed.next(true) is called when we destroy our particular component or service.
We use take() to keep our verbiage consistent so we can search for take or takeUntil across various components.
Long story short, yeah you might take a very slight performance hit piping your observables at each instance, but I highly recommend doing so in order to prevent any unwanted app-wide memory leak hunts. Then maybe if you have the time you can step through each one and see where .toPromise() actually unsubscribes correctly for you.
I have created this example to demonstrate the issue: https://stackblitz.com/edit/rxjs-vxvmq1?file=index.ts&devtoolsheight=100
Basically I want to call a function (e.g. cleanup as in the example) when a subject is completed. The function is called by others so not subscribing to the subject's changes, rather, a subscription is there to trigger the function for the completion of the subject. The function checks if it should do extra work when the subject is completed, so it checks if the subject has stopped or not. But it seems that once next() is called, the function is triggered before complete() is called, making the function thinks the subject has not been completed yet.
I wonder if there is any way to resolve this? Calling complete() first then next() didn't help as next() didn't notify the subscription after the subject has completed.
Long story short. It is all wrong.
Fix #1, not rxjs-idiomatic: you're subscribing wrongly: passing onNext callback, while you're interested in onComplete. There are three parameters taken by the subscribe: onNext, onError, onComplete and you are responsible for choosing the one you really need.
Fix #2, rxjs-idiomaric: you have to use pipe(...) along with operators defined in rxjs/operators (AFAIR). Note that it is easy to define your custom operator as well. So you either could use finalize(() => ...your cleanup logic goes here) or Observable.create returning finalizing logic as implementation of unsubscribe. Both nicely documented here or here.
In addition, seems that you're misunderstanding the semantics of rxjs. In terms of regex, it could be defined as next*(error|complete), which literally means: zero or infinitely many next's followed by either error or complete (exclusive or: never both simultaneously) exactly once. So don't expect next to do anything after complete (or equally error) fired.
I was playing around with the switchMap operator to clearly understand what was happening to a "switched" inner observable.
At first i thought that switchMap was only "unsubscribing" from the switched inner observable, but then i realize it was in fact "unsubscribing AND completing" the inner observable.
To confirm that i've written this small snippet:
https://codesandbox.io/s/relaxed-meninsky-c5jmw?fontsize=14
As you can see, the finalize() operator is correctly called when the subject emit for the second time, but:
why does the complete handler of the tap operator is not called ?
This somehow make feel only 80% happy with my understanding of this operator.
A related not on that:
I've read and watch numerous sources regarding switchMap, including:
This (great) ngconf sources: https://medium.com/#shairez/a-super-ninja-trick-to-learn-rxjss-switchmap-mergemap-concatmap-and-exhaustmap-forever-88e178a75f1b
The official rxjs doc: https://rxjs-dev.firebaseapp.com/api/operators/switchMap
And none of them clearly state if inner observable is unsubscribed or unsubcribed AND closed ( or at least i did not understand it :) )
I've watched the switchMap operator source code and there is no mention to takeXXX operator, how can he complete the inner operator without that ?
tl;dr
Do you confirm that switchMap complete inner observable when switching ?
Why does tap operator does not work as expected ?
If switchMap effectively complete inner observable how can he do that without using a takeXXX operator internally ?
I think you are confusing the difference between unsubscribe() and complete(). For a hot observable like a Subject you can "stop" it in a few ways. From the 'top->down' with complete() as you did in your example, or from the 'bottom->up' with unsubscribe().
switchMap() does exactly what it says, it switches from the primary observable to a secondary (or 'inner') observable. That is why when you complete() the outer observable, it has no effect on the inner one - the chain has been switched. To affect the chain (as opposed to just affecting the Subject which is the source observable), you need to get a reference to the Subscriber, and then call that Subscriber's unsubscribe() method.
To see this, I've forked your CodeSandbox and produced this new one
As you will see in that CodeSandbox I have added a few more lines to show what is going on:
Note the new tap() in the chain right above the switchMap - this will show what is going on directly from the Subject() before the chain is switched to a different Observable with the switchMap operator.
The Subscription for the chain is now being captured in the variable sub which can be unsubscribed later to affect the chain from the bottom->up.
Note that the s.complete() after 10 seconds is now reflected in the Subject, and note also how it doesn't affect the chain at all.
Now note that the new sub.unsubscribe() after 15 seconds indeed kills the chain.
uncomment the take(5) in the newT() method to see that indeed the tap's complete method will be called if the source above it actually completes (top->down).
finalize() catches the fact that an unsubscribe has happened (bottom->up), note that it occurs both when switchMap() does the automatic unsubscribe upwards when s.next() is called on the Subject source, as well as when unsubscribe() is called on the Subscription, which again causes a bottom->up termination. In no case is your complete() called in the original observer because the chain is never actually completed. You can complete the chain with a take(10) operator if you want, to see how that works as well.
Hopefully this helps clear up the confusion a little. :)
I'm wondering what are the differences between Observable.combineLatest and Observable.forkJoin?
As far as I can see, the only difference is forkJoin expects the Observables to be completed, while combineLatest returns the latest values.
Not only does forkJoin require all input observables to be completed, but it also returns an observable that produces a single value that is an array of the last values produced by the input observables. In other words, it waits until the last input observable completes, and then produces a single value and completes.
In contrast, combineLatest returns an Observable that produces a new value every time the input observables do, once all input observables have produced at least one value. This means it could have infinite values and may not complete. It also means that the input observables don't have to complete before producing a value.
forkJoin - When all observables are completed, emit the last emitted value from each.
combineLatest - When any observable emits a value, emit the latest value from each.
Usage is pretty similar, but you shouldn't forget to unsubscribe from combineLatest unlike forkJoin.
combineLatest(...)
runs observables in parallel, emitting a value each time an observable emits a value after all observables have emitted at least one value.
forkJoin(...)
runs observables in parallel, and emits a single value once all observables have completed.
Consideration for error handling:
If any of the observables error out - with combineLatest it will emit up to the point the error is thrown. forkJoin will just give back an error if any of the observables error out.
Advanced note: CombineLatest doesn't just get a single value for each source and move onto the next. If you need to ensure you only get the 'next available item' for each source observable you can add .pipe(take(1)) to the source observable as you add it to the input array.
There is a situation in Angular which would explain it better. Assume there is a change detection in Angular component, so the latest value is changed. In the pipe and tap methods of combineLatest, the code will be triggered as well. If the latest value is changed N times by the change detection, then the tap methods is also triggered N times as well.