Pipe RxJS observable to existing subject, but not on completion - rxjs

I implemented an RxJS architecture based on the answer to this question: Pipe RxJS observable to existing subject
Later, I noticed that the observable randomly closed and I've spent a lot of time to find the cause. In the end, appeared that this snippet was causing the closing:
const delayed = Observable.of(arr.shift()).delay(1000);
merge(otherObs, delayed).subscribe(mySubject);
The of/delay observable was completing after 1 second, and it piped the completion to mySubject, making it unable to receive other values, including the ones sent by otherObs in case they arrived after the timeout.
Here is a StackBlitz that shows the problem.
How can I avoid mySubject completes, without explicitly writing the callbacks?

This is a known issue, as solution I would suggest extending Subject, to prevent its completion as long as there are others observers listening to it, as demonstrated below:
class RescrictedCompletionSubject<T> extends Subject<T>{
public complete(): void {
if (this.observers.length <= 1) super.complete();
}
}
Optionally:
of(1).pipe(delay(1000)).subscribe(mySubject.next, mySubject.error);
Last but not least:
merge(otherObs, delayed).subscribe(mySubject);

Related

Do RxJS observers always process all events submitted before the observable is completed?

I want to make sure that all events, which were submitted before complete was invoked on the observable, are logged.
I'm aware that operators exist that stop emission of events (takeUntil, etc.) completely.
The question that I have is whether other operators exist which would lead to emissions not being sent if the complete on the subject is sent too 'early'. Are there cases where it would be beneficial to wait with the completion of the observable until the event was handled by the observer?
For example, are there situations (imagine any other RxJS operator instead of the delay) where the following code ...
const subj = new Subject<string>();
subj.pipe(delay(500))
.subscribe((val) => {
console.log(val);
subj.complete();
});
subj.next('1');
... makes more sense than that ...
const subj = new Subject<string>();
subj.pipe(delay(500))
.subscribe((val) => {
console.log(val);
});
subj.next('1');
subj.complete();
... when the subject should only emit one value?
Or is completing the subject immediately after next always safe in such situations?
If there are other factors I'm not aware of (e.g. synchronous vs. asynchronous execution of code) please mention them as well.
In general,
subj.next('1');
subj.complete();
is safe. As far as I know, none of the existing RxJS operators use a completion to cancel/unsubscribe observables early.
That being said, I can easily create such an operator myself. switchMap will cancel currently running inner observables when it receives it's next emission. You could, for example, create a custom operator that unsubscribes and exists early when it receives a complete event.
If your worried about that, however, you're out of luck. No matter what workaround you imagine, I can probably write an operator that will ruin your code. For example filter(_ => false) will stop the 1 from being emitted before the complete in either of the two cases you've described.
In the end, you and your coworkers must write good code (and test!) and RxJS doesn't change that.

Is it safe/okay to combine take(1) and defaultIfEmpty when subscribing to Observable<boolean>?

I am using the following code to basically ensure that I get a result from an Observable
this.authenticationService.isLoggedIn
.pipe(
take(1),
defaultIfEmpty(false)
)
.subscribe(result => return result);
Somehow it feels wrong to me, maybe because it seems sort of procedural.
Is this method okay? Will this get me in trouble in any way?
If in your code it's fine that this.authenticationService.isLoggedIn completes without an emit - then the code in your question is fine too.
If this.authenticationService.isLoggedIn emits anything at some point of time and completes after - then defaultIfEmpty is redundant.
It all depends on what isLoggedIn does.
It is clear that isLoggedIn returns an Observable.
Now, and Observable can do just 3 things
it can notify, i.e. emit, some data for consumption of Observers which are subscribed
it can raise an error
it can complete
So the first question is: how many times can isLoggedIn notify? Is it just one shot or is it a stream of notifications? If it can emit just one value and then complete, than the take(1) operator is useless.
But there is also the case that isLoggedIn never notifies and just completes. In this case a notification would never be signaled by the observer to its subscriber. Using defaultIfEmpty operator ensures that something is notified even in this case.
So, reading your code I understand that isLoggedIn can behave in these 2 ways
Emit more than once but you are interested only in the first notification
Never notify and just complete, in which case you want false to be returned
If this is not true, it may be the case that your code can be simplified.

Share operator that doesn't unsubscribe

I need to lazy load some infinite streams because they are expensive to start. And I also don't ever want to stop them once they are started for the same reason.
I'm thinking it would be neat if there was a share operator that didn't unsubscribe from the underlying stream ever once it is subscribed for the first time, even when all downstream subscribers unsubscribe.
Right now I'm doing it with a publish and a connect on two different lines, which works alright but just seems clunky and not very rxjs like:
public data$(): Observable<any> {
if (this.lazyData$) {
return this.lazyData$;
}
this.lazyData$ = this.someDataProvider.data$()
.publishReplay(1);
this.lazyData$.connect();
return this.lazyData$;
}
Also I want it to replay the last message to new subscribers as you see :)
The shareReplay operator was added in RxJS version 5.4.0. And, in version 5.5.0 a bug was fixed so that it maintains its history when its subscriber count drops to zero.
With the fix, shareReplay will effect the behaviour you are looking for, as it will now unsubscribe from the source only when the source completes or errors. When the number of subscribers to the shared observable drops to zero, the shared observable will remain subscribed to the source.
The behaviour of shareReplay has changed several times and a summary of the changes - and the reasons for them - can be found in this blog post.
As of RxJS 6.4.0, you can specify shareReplay refCount behavior.
source.pipe(
shareReplay({ bufferSize: 1, refCount: true })
)
shareReplay will unsubscribe from source after all subscribers have unsubscribed.

Why does Rxjs unsubscribe on error?

In short:
How to proceed listening after an error in stream without putting a .catch before every .subscribe?
If you need more details they are here:
Lets assume I have a Subject of current user or null. I get the data from API sometimes and send to the Subject. It updates the view accordingly.
But at some point error occurs on my server and I want my application to continue working as before but notify some places about the error and KEEP listening to my Subject.
Initially I thought that if I just do userSubject.error(...) it will only trigger .catch callback and error handlers on subscribes and skip all success handlers and chains.
And if after I call userSubject.next(...) all my chains and subscribers will work as before
BUT unluckily it is not the case. After the first uncaught .error it unsubscribes subscribers from the stream and they do not operate any more.
So my question: Why???
And what to do instead if I want to handle null value normally but also handle errors only in some places?
Here is the link to RxJs source code where Subscriber unsubscribes on error
https://github.com/ReactiveX/rxjs/blob/master/src/Subscriber.ts#L140
Rx observables follow the grammar next*(error|complete)?, meaning that they can produce nothing after error or complete notification has been delivered.
An explanation of why this matters can be found from Rx design guidelines:
The single message indicating that an observable sequence has finished ensures that consumers of the observable sequence can deterministically establish that it is safe to perform cleanup operations.
A single failure further ensures that abort semantics can be maintained for operators that work on multiple observable sequences.
In short, if you want your observers to keep listening to the subject after a server error has occurred, do not deliver that error to the subject, but rather handle it in some other way (e.g. use catch, retry or deliver the error to a dedicated subject).
Every Observable emits zero or more next notifications and one error or complete but never both.
For this reason, Subjects have internal state.
Then it depends how you construct your chain. For example you can use retry() to resubscribe to its source Observable on error.
Or when you pass values to your Subject you can send only next notifications and ignore the other two:
.subscribe(v => subject.next(v));
Or if you want to throw error when the user is null you can use any operator that captures exceptions and sends them as error notifications. For example like this:
.map(v => {
if (v === null) {
throw new Error("It's broken");
}
return v;
})
Anyway it's hard to give more precise advice without any code.

How to cache the result of a Task when using it as an Observable with retry?

This is what I have:
CitiesObservable = Observable
.FromAsync(apiClient.GetCitiesTask)
.Retry();
apiClient.GetCitiesTask returns a task of type: Task<List<City>>
The problem is that every time I add a subscriber to the observable, apiClient.GetCitiesTask gets called again. How can I cache the result once it has completed successfully?
Thanks
Question reworded
I want apiClient.GetCitiesTask to be called as many times as needed (until it doesn't fail), but once it success, all late subscribers should use a cached result.
Conclusion
2 solutions arose, one I found and the other (the selected answer).
Solution A: (actually is almost a solution)
CitiesObservable = Observable.FromAsync(apiClient.GetCitiesTask).Publish();
CitiesObservable.Connect();
// Then you can subscribe as you want. But! you won't receive the cached value on late subscribers, only the onCompleted signal.
Solution B: (by #Bluesman)
CitiesObservable = Observable.StartAsync(
() => Observable.FromAsync(apiClient.GetPlacesTask<City>).Retry().ToTask()
);
// Then you can subscribe as you want.
What about....
Observable
.StartAsync(() => Observable
.FromAsync(reserbusAPI.GetPlacesTask<City>)
.Retry()
.ToTask());
The outer StartAsync makes sure the eventual result from the created task is buffered while the inner FromAsync with Retry makes sure that GetPlacesTask is called as many times as needed. However, the whole retrying-thing still starts even before the first subscription.

Resources