I'm trying to return an Observable that is created asynchronously in a callback:
const mkAsync = (observer, delay) =>
setTimeout(() => Observable.of('some result').subscribe(observer), delay)
const create = arg => {
const ret = new Subject()
mkAsync(ret, arg)
return ret
}
Therefore I use a Subject as a unicast proxy which is subscribed to the underlying Observable in the callback.
The problem I have with this solution is that when I unsubscribe from the Subject's subsrciption the unsubscribe isn't forwarded to the underlying Observable. Looks like I need some type of refcounting to make the Subject unsubscribe when there are no more subscribers, but I wasn't able to figure it out when using it in this kind of imperative callback style.
I have to keep the mkAsync a void and am looking for an alternative implementation.
Is that the right way to do it? Is there an alternative solution to using a Subject?
How do I make sure that the created Observable is cancelled (unsubscribe is called on the Subscription) when the Subject is unsubscribed from?
This is pretty broad question and it's hard to tell what are you trying to achieve with this. I have two ideas:
The first thing is that there is refCount() operator that exists only on ConnectableObservable class that is returned from multicast (or publish) depending on the parameters you pass. See implementation for more details (basically if you don't set any selector function): https://github.com/ReactiveX/rxjs/blob/5.5.11/src/operators/multicast.ts
The second issue I can think of is that you're doing basically this:
const ret = new Subject()
Observable.of(...).subscribe(ret);
The problem with this is that .of will emit immediately it's next items and then it sends the complete notification. Subjects have internal state and when Subject receives the complete notification it marks itself as stopped and it will never ever emit anything.
I'm suspicious that's what's happening to you. Even when you return the Subject instance with return ret and later probably subscribe to it you still won't receive anything because this Subject has already received the complete notification.
Related
From the RxJS documentation I see the following example:
const source = interval(1000);
const clicks = fromEvent(document, 'click');
const result = source.pipe(takeUntil(clicks));
result.subscribe(x => console.log(x));
This is close to a code pattern needed for my app but I see a problem. The takeUntil operator subscribes, but as I understand it an Observer has no way to unsubscribe from the source Observable. It has no access to a Subscription object on which it can call unsubscribe().
So if I understand this correctly then once the user clicks the source observable will continue to emit ticks forever to the takeUntil which will consume them and do nothing with them.
Am I reading this correctly? If so is there a generally accepted way to kill the source observable from within the Observer pipe?
What happens with takeUntil is the following.
When the Observable passed to takeUntil as parameter notifies a value, the subscriber of the Observable returned by takeUntil completes and, as a consequence, all the subscriptions created in the pipe chain are unsubscribed one after the other in reverse order.
In simpler words, the unsubscription is performed behind the scene by the RxJs internal mechanisms.
To prove this behavior you can try this code
const source = interval(1000).pipe(
tap({ next: (val) => console.log('source value', val) })
);
const clicks = fromEvent(document, 'click');
const result = source.pipe(takeUntil(clicks));
result.subscribe((x) => console.log(x));
If you run it, you see that the message 'source value', val is printed until the click occurs. After this, no more message is printed on the console, which means that the Observable upstream, i.e. the Observable created by the interval function does not notify any more.
You can try the above code in this stackblitz.
SOME DETAILS ON THE INTERNALS
We can take a look at the internals of the RxJs implementation to see how this unsubscribe behind the scenes works.
Let's start from takeUntil. In its implementation we see a line like this
innerFrom(notifier).subscribe(new OperatorSubscriber(subscriber, () => subscriber.complete(), noop));
which, in essence, says that as soon as the notifier (i.e. the Observable passed to takeUntil as parameter) notifies, the complete method is called on the subscriber.
The invocation of the complete method triggers many things, but eventually it ends up calling the method execTeardown of Subscription which ends up invoking unsubscribe of OperatorSubscriber which itself calls unsubscribe of Subscription.
As we see, the chain is pretty long and complex to follow, but the core message is that the tearDown logic (i.e. the logic which is invoked when an Observable completes, errors or is unsubscribed) calls the unsubscription logic.
Maybe it is useful to look at one more thing, an implementation of a custom operator directly from the RxJs documentation.
In this case, at the end of the definition of the operator, we find this piece of code
// Return the teardown logic. This will be invoked when
// the result errors, completes, or is unsubscribed.
return () => {
subscription.unsubscribe();
// Clean up our timers.
for (const timerID of allTimerIDs) {
clearTimeout(timerID);
}
};
This is the teardown logic for this custom operator and such logic invokes the unsubscribe as well as any other cleanup activity.
I am trying to determining what to do with the following code
let sub = myObservable.subscribe(
v => doThing(v),
e => handle(e),
() => sub.unsubscribe(),
)
The issue is
1. This code is incorrect because of myObservable completed synchronously, an NPE would be thrown on completion.
2. Even though I suspect that the unsubscribe call here is good practice. I can't help but feel it might not be necessary because I have not see it done anywhere else.
I have read this article https://blog.angularindepth.com/why-you-have-to-unsubscribe-from-observable-92502d5639d0 but it actually leaves me more confused than when I started.
If I do
let subA = myObservable.pipe(take(1)).subscribe()
let subB = myObservable.pipe(takeUntil(foo)).subscribe()
Do I not need to unsubscribe subA and subB anymore?
how about subC over here?
let subC = myObservable.pipe(finalize(() => cleanupOtherResources())).subscribe()
Or do I have to add all subscription into a list in every class that calls subscribe() on any BehaviorSubject and unsubscribe them at once?
Thanks!
It is always best practice to unsubscribe. takeUntil is fine to use if you know that the clean up method of your class actually emits on the cleanup observable. take is not always guaranteed that the observable has emitted. There may be cases where you know that observable will definitely emit at least once but there is still a possibility that a leak has been created.
The problem with assuming that an observable will complete is that you don't know if the internals of the service returning the observable change. If you assume that the observable is a http request and completes at the end of the request then a future refactor that changes the observable to a cache handler has now created a memory leak because you didn't unsubscribe.
Unsubscribing also cancels any on going requests.
The problem with statements like
let sub = myObservable.subscribe(
v => doThing(v),
e => handle(e),
() => sub.unsubscribe(),
)
If myObservable emits instantly like a BehaviorSubject would then sub is undefined. I would avoid self unsubscribing like that and instead use a takeUntil with a subject.
const finalise$ = new Subject();
myObservable.pipe(takeUntil(finalise$)).subscribe(
v => doThing(v),
e => handle(e),
() => { finalise$.next(); },
);
This code is guaranteed to be self unsubscribe safe.
I'm new in RxSwift, I don't understand what is difference between do(onNext:) and subscribe(onNext:).
I google it but did't found good resources to explain the difference.
At the beginning of a cold Observable chain there is a function that generates events, for e.g. the function that initiates a network request.
That generator function will not be called unless the Observable is subscribed to (and by default, it will be called each time the observable is subscribed to.) So if you add a do(onNext:) to your observable chain, the function will not be called and the action that generates events will not be initiated. You have to add a subscribe(onNext:) for that to happen.
(The actual internals are a bit more complex than the above description, but close enough for this explanation.)
The do operator allows you to insert side effects; that is, handlers to do things that will not change the emitted event in any way. do will just pass the event through to the next operator in the chain.
The method for using the do operator is here.
And you can provide handlers for any or all of these events.
Let's say We have an observable that never emits anything. Even though it emits nothing, it is still an observable and we can subscribe to it. do operator allows us to do something when a subscription was made to it.
So below example will print "Subscribed" when a subscription was made to that observable.
Feel free to include any of the other handlers if you’d like; they work just like subscribe’s handlers do
let observable = Observable<Any>.never()
let disposeBag = DisposeBag()
observable
.do(onSubscribe: {
print("Subscribed")
})
.subscribe(
onNext: { element in
print(element)
},
onCompleted: {
print("Completed")
},
onDisposed: {
print("Disposed")
}
)
.disposed(by: disposeBag)
I'm working on something that is recording data coming from a queue. It was easy enough to process the queue into an Observable so that I can have multiple endpoints in my code receiving the information in the queue.
Furthermore, I can be sure that the information arrives in order. That bit works nicely as well since the Observables ensure that. But, one tricky bit is that I don't want the Observer to be notified of the next thing until it has completed processing the previous thing. But the processing done by the Observer is asynchronous.
As a more concrete example that is probably simple enough to follow. Imagine my queue contains URLs. I'm exposing those as an Observable in my code. The I subscribe an Observer whose job is to fetch the URLs and write the content to disk (this is a contrived example, so don't take issue with these specifics). The important point is that fetching and saving are async. My problem is that I don't want the observer to be given the "next" URL from the Observable until they have completed the previous processing.
But the call to next on the Observer interface returns void. So there is no way for the Observer to communicate back to me that has actually completed the async task.
Any suggestions? I suspect there is probably some kind of operator that could be coded up that would basically withhold future values (queue them up in memory?) until it somehow knew the Observer was ready for it. But I was hoping something like that already existed following some established pattern.
similar use case i ran into before
window.document.onkeydown=(e)=>{
return false
}
let count=0;
let asyncTask=(name,time)=>{
time=time || 2000
return Rx.Observable.create(function(obs) {
setTimeout(function() {
count++
obs.next('task:'+name+count);
console.log('Task:',count ,' ', time, 'task complete')
obs.complete();
}, time);
});
}
let subject=new Rx.Subject()
let queueExec$=new Rx.Subject()
Rx.Observable.fromEvent(btnA, 'click').subscribe(()=>{
queueExec$.next(asyncTask('A',4000))
})
Rx.Observable.fromEvent(btnB, 'click').subscribe(()=>{
queueExec$.next(asyncTask('B',4000))
})
Rx.Observable.fromEvent(btnC, 'click').subscribe(()=>{
queueExec$.next(asyncTask('C',4000))
})
queueExec$.concatMap(value=>value)
.subscribe(function(data) {
console.log('onNext', data);
},
function(error) {
console.log('onError', error);
},function(){
console.log('completed')
});
What you describe sounds like "backpressure". You can read about it in RxJS 4 documentation https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/backpressure.md. However this is mentioning operators that don't exist in RxJS 5. For example have a look at "Controlled Observables" that should refer to what you need.
I think you could achieve the same with concatMap and an instance of Subject:
const asyncOperationEnd = new Subject();
source.concatMap(val => asyncOperationEnd
.mapTo(void 0)
.startWith(val)
.take(2) // that's `val` and the `void 0` that ends this inner Observable
)
.filter(Boolean) // Always ignore `void 0`
.subscribe(val => {
// do some async operation...
// call `asyncOperationEnd.next()` and let `concatMap` process another value
});
Fro your description it actually seems like the "observer" you're mentioning works like Subject so it would make maybe more sense to make a custom Subject class that you could use in any Observable chain.
Isn't this just concatMap?
// Requests are coming in a stream, with small intervals or without any.
const requests=Rx.Observable.of(2,1,16,8,16)
.concatMap(v=>Rx.Observable.timer(1000).mapTo(v));
// Fetch, it takes some time.
function fetch(query){
return Rx.Observable.timer(100*query)
.mapTo('!'+query).startWith('?'+query);
}
requests.concatMap(q=>fetch(q));
https://rxviz.com/v/Mog1rmGJ
If you want to allow multiple fetches simultaneously, use mergeMap with concurrency parameter.
I have at least two buttons that I want to dynamically listen for clicks on. listeningArray$ will emit an array (ar) of button #'s that I need to be listening to. When somebody clicks on one of these buttons I'm listening to, I need to console log that the button that was clicked and also log the value from a time interval.
If ar goes from [1,2] to [1], we need to stop listening to clicks on button #2. So the DOM click event needs to be removed for 2 and that should trigger the .finally() operator. But for 1, we should remain subscribed and the code inside the .finally() should not run, since nothing is being unsubscribed.
const obj$ = {};
Rx.Observable.combineLatest(
Rx.Observable.interval(2000),
listeningArray$ // Will randomly emit either [1] or [1,2]
)
.switchMap(([x, ar]) => {
const observables = [];
ar.forEach(n => {
let nEl = document.getElementById('el'+n);
obj$[n] = obj$[n] || Rx.Observable.fromEvent(nEl, 'click')
.map(()=>{
console.log(' el' + n);
})
.finally(() => {
console.log(' FINALLY_' + n);
});
observables.push(obj$[n]);
})
return Rx.Observable.combineLatest(...observables);
})
.subscribe()
But what's happening is every time the interval emits a value, the DOM events ALL get removed and then immediately get added on again, and the code inside the .finally operator runs for 1 and 2.
This is really frustrating me. What am I missing?
It's a bit of a complex situation, so I created this: https://jsfiddle.net/mfp22/xtca98vx/7/
I was actually really close, but I misunderstood the point of switchMap.
switchMap is designed to unsubscribe from the observable it returns whenever a new value is emitted from above. This is why it can be used to cancel old pending Http requests when a new request needs to be made instead.
The problem I was having is to be expected. switchMap will unsubscribe from the previously returned observable before subscribing to the current one. This was unacceptable, as I explained in the question. The reason this was unacceptable was that in my actual project, the fromEvent observables were listening to Firebase child_added events, so when these cold observables went from having no subscribers to having 1 subscriber, Firebase would subsequently fire the event for every child already existing, as well as for future ones added.
I played with mergeMap for a while, but it was really difficult and buggy to manually have to unsubscribe from previously returned observables.
So I added a subscriber for the inner observables while switchMap was doing its process of unsubscribe from old => subscribe to new so that there would always be a subscriber. I used takeUntil(Observable.timer(0)) to make sure the subscribers didn't build up and cause a memory leak.
There may be a better solution, but this was the best one I found.
const obj$ = {};
Rx.Observable.combineLatest(
Rx.Observable.interval(2000),
listeningArray$ // Will randomly emit either [1] or [1,2]
)
.switchMap(([x, ar]) => {
const observables = [];
ar.forEach(n => {
let nEl = document.getElementById('el'+n);
obj$[n] = obj$[n] || Rx.Observable.fromEvent(nEl, 'click')
.map(()=>{
console.log(' el' + n);
})
.finally(() => {
console.log(' FINALLY_' + n);
})
.share();
obj$[n].takeUntil(Rx.Observable.timer(0))
.subscribe();
observables.push(obj$[n]);
})
return Rx.Observable.combineLatest(...observables);
})
.subscribe()
I also had to add the .share() method. I was going to need it anyway. I'm using this pattern to let some Angular components declare what data they need, ignoring what other components might want, to achieve a better separation of concerns. So multiple components can subscribe to the same Firebase observables, but the .share() operator ensures that each message from Firebase is only handled once (I'm dispatching actions to a Redux store for each one).
Working solution: https://jsfiddle.net/mfp22/xtca98vx/8/
State in FRP is immutable. Thus when you switchMap to the second emission the previous observable combineLatest containing [1,2] will get unsubscribed and the finally operator invoked. Before subscribing to the next containing only [1]
If you only want to unsubscribe from one button you can store state in the DOM (add atr to button) and use filter to ignore button.
Or you can add a TakeWhile() to every button dictating when it should be unsubscribed so it can invoke it's own finally()