RxJs : Weird behavior with takeUntil? - rxjs

Nothing too much serious here, just curious.
I wanted to make an example for something and came up with this code :
const { Observable, Subject } = Rx
const timeout$ = new Subject()
const obs$ = Observable
.of(1)
.takeUntil(timeout$)
.delay(2000)
.subscribe(x => console.log(x))
timeout$.next()
timeout$.complete()
I thought this code would not display the console.log but it does.
Can someone explain me why ?
Is this a bug or just a bad understanding from me about takeUntil ?
Here's a Plunkr to demo https://plnkr.co/edit/wpKztBabnBeIuNZS28wu?p=info

Notice that if you switch the order of the takeUntil() and delay() it won't be emitted as expected:
Observable
.of(1)
.delay(2000)
.takeUntil(timeout$)
.subscribe(x => console.log(x));
What you see happens because the delay() schedules the emission before that takeUntil() sends complete notification.
Right now in RxJS 5.2.0 the takeUntil() operator doesn't force unsubscribe from its source which is the problem here. So when the takeUntil() is notified by timeout$ it remains subscribed to its source Observable and therefore the scheduled emission from delay() isn't disposed.
Notice there's no this.unsubscribe() call when handling emission from the notification Observable: https://github.com/ReactiveX/rxjs/blob/master/src/operator/takeUntil.ts#L61
There're other operators with the same behavior. For example first() and takeWhile().
On the other hand for example the take() operator does unsubscribe:
https://github.com/ReactiveX/rxjs/blob/master/src/operator/take.ts#L80
This behavior is in fact reported and whether this is a bug or not is being discussed right now:
https://github.com/ReactiveX/rxjs/issues/2455
https://github.com/ReactiveX/rxjs/pull/2470
https://github.com/ReactiveX/rxjs/pull/2463
https://github.com/ReactiveX/rxjs/pull/2457

Related

How does CombineLatest work compared to of

I'm learning about forkJoin. I'm trying to wrap my head around how it works compared to creating an observable with of. Please tell me if I have this right:
When I use of, it creates an observable that will emit the value you pass to it right away. So if I have this:
const obs = of('hello');
obs.subscribe(console.log);
...it will log 'hello' as soon as the line where I subscribe to obs executes. Is this correct?
Now if I have this:
const obs1 = httpClient.get(url1);
const obs2 = httpClient.get(url2);
const fjObs = forkJoin({obs1, obs2});
fjObs.subscribe(({obs1, obs2}) => console.log(`obs1=${obs1}, obs2=${obs2}`));
...it WON'T log obs1 and obs2 as soon as the line where I subscribe to fjObs executes. It will log them only when both obs1 and obs2 have completed, which could be a while after I subscribe to fjObs. Is this correct?
And until that happens, fjObs is just an observable that has not yet emitted any values. Is that correct?
Please let me know if my understanding is correct. Thank you.
Yes you are basically correct. Please note that forkJoin accepts an array of observables, so your code should be: const fjObs = forkJoin([obs1, obs2]); as pointed out in the comment, this assumption was wrong
also you could test this:
forkJoin([of('one'), of('two')]).subscribe(console.log);
in this case the console.log will be executed immediatly since both of emits immediatly.
you could also do: forkJoin([of('one'), httpClient.get(url1)]).subscribe(console.log); in this case it will log after the HTTP request is completed.
You should not compare of with forkJoin since those are very different concepts.
of creates an observable and forkJoin combines an array of observables and emits (the last value of each observable) when all have emitted a value completed

Do the take() and takeUntil() RxJS cause a memory leak?

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.

DebounceTime after first value in RxJS

I need a specific behavior that I can't get with the RxJS operators. The closest would be to use DebounceTime only for values entered after the first one, but I can't find a way to do it. I have also tried with ThrottleTime but it is not exactly what I am looking for, since it launches intermediate calls, and I only want one at the beginning that is instantaneous, and another at the end, nothing else.
ThrottleTime
throttleTime(12 ticks, { leading: true, trailing: true })
source: --0--1-----2--3----4--5-6---7------------8-------9---------
throttle interval: --[~~~~~~~~~~~I~~~~~~~~~~~I~~~~~~~~~~~I~~~~~~~~~~~]--------
output: --0-----------3-----------6-----------7-----------9--------
source_2: --0--------1------------------2--------------3---4---------
throttle interval: --[~~~~~~~~~~~I~~~~~~~~~~~]---[~~~~~~~~~~~]--[~~~~~~~~~~~I~
output_2: --0-----------1---------------2--------------3-----------4-
DebounceTime
debounceTime(500)
source: --0--1--------3------------4-5-6-7-8-9-10-11--13----------------
debounce_interval: -----[~~~~~]--[~~~~~]--------------------------[~~~~~]----------
output: -----------1--------3--------------------------------13---------
What I want
debounceTimeAfterFirst(500) (?)
source: --0--1--------3------------4-5-6-7-8-9-10-11--13----------------
debounce_interval: -----[~~~~~]--[~~~~~]--------------------------[~~~~~]----------
output: --0--------1--3------------4-------------------------13---------
As you see, the debounce time is activated when a new value is entered. If the debounce time passes and any new value has been entered, it stops the listening the debounceTime action and waits to start a new one.
Edit: I forgot to comment that this must be integrated with NgRx’s Effects, so it must be a continuous stream that mustn't be completed. Terminating it would probably cause it to stop listening for dispatched actions.
I would use a throttle combined with a debounceTime:
throttle: from Documentation Emit value on the leading edge of an interval, but suppress new values until durationSelector has completed.
debounceTime: from Documentation Discard emitted values that take less than the specified time between output.
I would use a throttle stream to get the raising edge (the first emission) and then the debounce stream would give us the falling edge.
const source = fromEvent(document.getElementsByTagName('input'), 'keyup').pipe(
pluck('target', 'value')
);
const debounced = source.pipe(
debounceTime(4000),
map((v) => `[d] ${v}`)
);
const effect = merge(
source.pipe(
throttle((val) => debounced),
map((v) => `[t] ${v}`)
),
debounced
);
effect.subscribe(console.log);
See RxJS StackBlitz with the console open to see the values changing.
I prepared the setup to adapt it to NgRx which you mention. The effect I got working is:
#Injectable({ providedIn: 'root' })
export class FooEffects {
switchLight$ = createEffect(() => {
const source = this.actions$.pipe(
ofType('[App] Switch Light'),
pluck('onOrOff'),
share()
);
const debounced = source.pipe(debounceTime(1000), share());
return merge(source.pipe(throttle((val) => debounced)), debounced).pipe(
map((onOrOff) => SetLightStatus({ onOrOff }))
);
});
constructor(private actions$: Actions) {}
}
See NgRx StackBlitz with the proposed solution working in the context of an Angular NgRx application.
share: This operator prevents the downstream paths to simultaneously fetch the data from all the way up the chain, instead they grab it from the point where you place share.
I also tried to adapt #martin's connect() approach. But I don't know how #martin would "reset" the system so that after a long time if a new source value is emitted would not debounce it just in the same manner as you first run it, #martin, feel free to fork it and tweak it to make it work, I'm curious about your approach, which is very smart. I didn't know about connect().
#avicarpio give it a go on your application and let us know how it goes :)
I think you could do it like the following, even though I can't think of any easier solution right now (I'm assuming you're using RxJS 7+ with connect() operator):
connect(shared$ => shared$.pipe(
exhaustMap(value => merge(
of(value),
shared$.pipe(debounceTime(1000)),
).pipe(
take(2),
)),
)),
Live demo: https://stackblitz.com/edit/rxjs-qwoesj?devtoolsheight=60&file=index.ts
connect() will share the source Observable and lets you reuse it in its project function multiple times. I'm using it only to use the source Observable inside another chain.
exhaustMap() will ignore all next notifications until its inner Observable completes. In this case the inner Observable will immediately reemit the current value (of(value)) and then use debounceTime(). Any subsequent emission from source is ignored by exhaustMap() because the inner Observable hasn't completed yet but is also passed to debounceTime(). Then take(2) is used to complete the chain after debounceTime() emits and the whole process can repeat when source emits because exhaustMap() won't ignore the next notification (its inner Observable has completed).
Here's a custom operator that (as far s I can tell) does what you're after.
The two key insights here are:
Use connect so that you can subscribe to the source twice, once to ignore emissions with exhaustMap and another to inspect and debounce emissions with switchMap
Create an internal token so that you know when to exit without a debounced emission. (Insures that from your example above, the 4 is still emitted).
function throttleDebounceTime<T>(interval: number): MonoTypeOperatorFunction<T> {
// Use this token's memory address as a nominal token
const resetToken = {};
return connect(s$ => s$.pipe(
exhaustMap(a => s$.pipe(
startWith(resetToken),
switchMap(b => timer(interval).pipe(mapTo(b))),
take(1),
filter<T>(c => c !== resetToken),
startWith(a)
))
));
}
example:
of(1,2,3,4).pipe(
throttleDebounceTime(500)
).subscribe(console.log);
// 1 [...0.5s wait] 4

Does RxJs .first() operator (among others) complete the source observable?

If I have the following code:
const subject = new BehaviorSubject<[]>([]);
const observable = subject.asObservable();
subject.next([{color: 'blue'}])
observable.pipe(first()).subscribe(v => console.log(v))
According to the docs:
If called with no arguments, first emits the first value of the source Observable, then completes....
Does this mean that the source observable(the BehaviorSubject in this case) completes and you can no longer use it? As in you can no longer call .next([...]) on it.
I'm trying to understand how can an observable complete if it doesnt have the .complete() method on it?
I was trying to look at the source code of first() which under the covers uses take() and in turn take() uses lift() so I was curious if somehow first operator returns a copy of the source observable(the subject) and completes that.
The source observable is not completing, what it completes is the subscription. You could have multiple subscriptions on your Observable source, in your case one BehaviorSubject.
subject.next([{color: 'blue'}])
subject.next([{color: 'red'}])
const subs1 = observable.pipe(first()).subscribe(v => console.log(v))
const subs2 = observable.subscribe(v => console.log(v))
In the example above you clearly see that the source is not completing, just the subscription.
I have created a Stackblitz if you want to try it: https://stackblitz.com/edit/rxjs-uv6h6i
Hope I got your point!
Cheers :)

Is it necessary to unsubscribe everytime I subscribe

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.

Resources