I have some javascript:
this.mySubscription = someObservable.subscribe((obs: any) => {
this.mySubscription.unsubscribe();
this.mySubscription = undefined;
}
on execution, the console logs the error ERROR TypeError: Cannot read property 'unsubscribe' of undefined.
I wonder why I can not unsubscribe inside the subscribe lambda function. Is there a correct way to do so? I have read a bit about using dummy-subjects and completing them or using takeUntil/takeWhile and other pipe operators workArounds.
What is a correct way/workaround to unsubscribe a subscription inside the subscription's subscribe-function?
I am currently using a dummy subscription like so:
mySubscription: BehaviorSubject<any> = new BehaviorSubject<any>(undefined);
// when I do the subscription:
dummySubscription: BehaviorSubject<any> = new BehaviourSubject<any>(this.mySubscription.getValue());
this.mySubscription = someObservable.subscribe((obs: any) => {
// any work...
dummySubscription.next(obs);
dummySubscription.complete();
dummySubscription = undefined;
}, error => {
dummySubscription.error(error);
});
dummySubscription.subscribe((obs: any) => {
// here the actual work to do when mySubscription emits a value, before it should have been unsubscribed upon
}, err => {
// if errors need be
});
You shouldn't try to unsubscribe in the subscribe function.
You can unsubscribe with operators like take, takeWhile or takeUntil.
take
Use take(n) to unsubscribe after someObservable emits n times.
someObservable.pipe(
take(1)
).subscribe(value => console.log(value));
takeWhile
Use takeWhile to unsubscribe when an emitted value fails a condition.
someObservable.pipe(
takeWhile(value => valueIsSave(value))
).subscribe(value => console.log(value));
valueIsSave(value): boolean {
// return true if the subscription should continue
// return false if you want to unsubscribe on that value
}
takeUntil
Use takeUntil(obs$) to unsubscribe when the observable obs$ emits.
const terminate = new Subject();
someObservable.pipe(
takeUntil(terminate)
).subscribe(value => console.log(value));
unsub() {
terminate.next() // trigger unsubscribe
}
If you make your stream asynchronous, what you're doing should work. For example, this will not work:
const sub = from([1,2,3,4,5,6,7,8,9,10]).subscribe(val => {
console.log(val);
if(val > 5) sub.unsubscribe();
});
but this will work:
const sub2 = from([1,2,3,4,5,6,7,8,9,10]).pipe(
delay(0)
).subscribe(val => {
console.log(val);
if(val > 5) sub2.unsubscribe();
});
Because the JS event loop is fairly predictable (blocks of code are always run to completion), If any part of your stream is asynchronous, then you can be sure that your subscription will be defined before your lambda callback is invoked.
Should you do this?
Probably not. If your code relies on the internal (otherwise hidden) machinations of your language/compiler/interpreter/etc, you've created brittle code and/or code that is hard to maintain. The next developer looking at my code is going to be confused as to why there's a delay(0) - that looks like it shouldn't do anything.
Notice that in subscribe(), your lambda has access to its closure as well as the current stream variable. The takeWhile() operator has access to the same closure and the same stream variables.
from([1,2,3,4,5,6,7,8,9,10]).pipe(
takeWhile(val => {
// add custom logic
return val <= 5;
})
).subscribe(val => {
console.log(val);
});
takeWhile() can to anything that sub = subscribe(... sub.unsubscibe() ... ), and has the added benefit of not requiring you to manage a subscription object and being easier to read/maintain.
Inspired by another answer here and especially this article, https://medium.com/#benlesh/rxjs-dont-unsubscribe-6753ed4fda87, I'd like to suggest takeUntil() with following example:
...
let stop$: Subject<any> = new Subject<any>(); // This is the one which will stop the observable ( unsubscribe a like mechanism )
obs$
.pipe(
takeUntil(stop$)
)
.subscribe(res => {
if ( res.something === true ) {
// This next to lines will cause the subscribe to stop
stop$.next();
stop$.complete();
}
});
...
And I'd like to quote sentence RxJS: Don’t Unsubscribe from those article title mentioned above :).
Related
I have a stream of events coming through via fromEventPattern like so:
fromEventPattern<IPsEvent>(addEventHandler).subscribe(ps$);
Due to business quirks, I expect that I will sometimes get an exception thrown, at which point I want to queue up the events and refire once that error state is resolved.
I've been trying the solution from Pausable buffer with RxJS to no avail. I am thinking it's because they are able to toggle through a separate observable whereas this is kind of asking to pause itself midstream. In the linked example I have blockingCallsAllowed$ rather than autoSave$. Here is my latest try:
const source$ = new Subject<IPsEvent>();
const blockingCallsAllowed$ = new BehaviorSubject(true);
const on$ = blockingCallsAllowed$.pipe(filter((v) => v));
const off$ = blockingCallsAllowed$.pipe(filter((v) => !v));
source$
.pipe(
map(() => {
try {
// line will throw exception at certain times
myFunction();
return true;
} catch (e) {
const i = setInterval(() => {
try {
myFunction();
console.log('good again');
blockingCallsAllowed$.next(true);
clearInterval(i);
} catch (er) {
// still in flux
}
}, 50);
return false;
}
}),
)
.subscribe(blockingCallsAllowed$);
const output$ = merge(
source$.pipe(bufferToggle(off$, () => on$)),
source$.pipe(windowToggle(on$, () => off$)),
).pipe(concatMap(from));
output$.subscribe((evt) => {
console.log('After buffers', evt);
});
// Add events from the Ps API to the event stream
fromEventPattern(addEventHandler).subscribe(source$);
Everything fires fine until the first exception and then it never outputs what it had buffered away, even though it fires that things are good again in console.log.
I am thinking there is some timing issue around relying on source$.pipe in the same execution and then the interval running later with .next. Can't nail it though after many different permutations of this code.
It's not clear to me what you're trying to implement. Though if you want to keep retrying myFunction() every 50ms until it succeeds and stop processing other events while this happens, concatMap basically does all that for you.
It will buffer emissions from the source while it waits for the inner observable to complete.
So what you're after might look like this:
source$.pipe(
concatMap(_ => of(true).pipe(
tap(_ => myFunction()),
retryWhen(errors => errors.pipe(
delay(50)
))
))
).subscribe();
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));
What I have been playing with is to use combineLatest with concatAll() but they are still being called simultaneously. I could just loop and call each but I am always wondering if there is a better way within the RXJS workflow.
combineLatest(arrayOfApiObservables).pipe(concatAll()).subscribe();
The problem here is that you use the combineLatest operator, which will emit value only after all observables had emitted (e.g. it is calling everything simultaneously).
After that the concatAll can't affect the arrayOfApiObservables because they have alredy been called.
The right aproach is to create a higher-order observable (observable that emits observables), which can be achived with the help of the operator from and after that you can concatAll them to achive the desired result.
concatAll definition as seen in the docs: Converts a higher-order Observable into a first-order Observable by concatenating the inner Observables in order..
let {
interval,
from
} = rxjs
let {
take,
concatAll,
mapTo
} = rxjs.operators
let ref = document.querySelector('#container')
const obs1$ = interval(1000).pipe(take(1), mapTo('obs1'));
const obs2$ = interval(500).pipe(take(1), mapTo('obs2'));
const obs3$ = interval(2000).pipe(take(1), mapTo('obs3'));
let allObservables$ = from([obs1$, obs2$, obs3$])
allObservables$.pipe(
concatAll()
).subscribe((x) => {
console.log(x)
container.innerHTML += `<div>${x}</div>`
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.js"></script>
<div id="container"></div>
If you want them to be executed in a sequential manner, you could use concatMap() inside pipe().
Something like this:
of(1)
.pipe(
concatMap(result => {
console.log(result);
return of(2);
}),
concatMap(result => {
console.log(result);
return of(3);
}),
concatMap(result => {
console.log(result);
return of(4);
}),
concatMap(result => {
console.log(result);
return of(5);
}),
concatMap(result => {
console.log(result);
return of(6);
})
)
.subscribe(res => {
console.log("finish");
});
But, if you want to execute them all at once and then await for them until they are all completed, then just use forkJoin().
I am implementing an observable which can be subscribed to before it is "assigned" Think of it like hoisting an observable definition so I dont have to worry about the order in which I create observables derived from other observables, I call it a ColdSubject.
ColdSubject works fine (I can add observables to it, and only when somebody subscribes to the ColdObservable do its operators get evaluated).
However withLatestFrom will never emit while waiting for obs$, despite the observable it's "waiting for" emitting to a subscriber several times!
export class ColdSubject<T> {
// If you subscribe to this before an observable has been added to $acc, you will be notified as soon as one is added, and if you subscribe to this after an observable is added to acc$ you will also be notified
public obs$: Observable<T>;
public acc$ = new BehaviorSubject<Observable<T>>(merge());
constructor() {
this.obs$ = this.acc$.pipe(switchMap(v => v));
}
addObservable(newObservable: Observable<T>) {
this.acc$.next(merge(this.acc$.getValue(), newObservable))
}
}
const foo = new ColdSubject<number>();
# I know this observable is waiting for withLatestFrom because "Tap yeet" is logged
of('yeet').pipe(
tap(v => console.log(`tap ${v}`)),
withLatestFrom(foo.obs$)
).subscribe(v => {
console.log(`WithLatestFrom ${v}`);
});
# This observable will begin emitting 5 seconds into the script, because I wait 5 seconds to subscribe to it
foo.addObservable(
interval(1000).pipe(
take(5),
tap(v => console.log(`Interval ${v}`))
)
);
# Subscribe 5 seconds into script start, so I know that my ColdSubject only evaluates its observables once they're subscribed to
setTimeout(
() => foo.obs$.subscribe(v => console.log(`Subscribe ${v}`)),
5000
);
Why does foo.obs$ emit several times, while the operation waiting for its latest value not emit?
Looking at the source code one can see that withLatestFrom is triggered by_next which is fired by the source Observable calling next:
protected _next(value: T) {
if (this.toRespond.length === 0) {
/**
* value - emitted by the source Observable
* ...this.values - emitted by the Observables passed to `withLatestFrom`
*/
const args = [value, ...this.values];
if (this.project) {
this._tryProject(args);
} else {
this.destination.next(args);
}
}
}
Your issue is that your source completes right away, while the Observable passed to withLatestFrom has not emitted yet. by the time foo.obs emits, your source Observable has long since completed.
What I would recommend of using in your case is combineLatest as demonstrated below:
combineLatest(of("yeet"), foo.obs$)
.pipe(
tap(v => console.log(`tap ${v}`)),
)
.subscribe(v => {});
of('yeet') emits and is complete so withLatestFrom will complete as the source is complete.
Change your subscription to
of('yeet').pipe(
tap(v => console.log(`tap ${v}`)),
withLatestFrom(foo.obs$)
).subscribe({ complete: () => console.log('yeet complete') });
and you will see it is infact complete.
https://stackblitz.com/edit/rxjs-6-opeartors-2nuam1?file=index.ts
Consider the following:
a$ = someObservable$.pipe(
switchMap(data => liveForEver$)
);
a$.subscribe();
a$.unsubscribe();
Now, liveForEver$ as the name suggests is subscribed to by other parts of the code. Could it be that a$ will stay subscribed after a$ is unsubscribed because switchMap returns a 'living' observable?
When an operator is defined, it usually has behavior to unsubscribe to child subscriptions when it is unsubscribed to. If you make a custom operator and fail to do this, then you'll likely create memory leaks. Consider the following custom operator:
function timesTwo(input$: Observable<number>): Observable<number> {
return new Observable<number>(observer => {
input$.subscribe({
next: val => observer.next(val * 2),
complete: () => observer.complete(),
error: err => observer.error()
});
return {
// I should $input.unsubscribe()
unsubscribe: () => {/*Do Nothing*/}
}
});
}
function timesTwoPipeable<T>(): MonoTypeOperatorFunction<T> {
return input$ => timesTwo(input$);
}
Here I've created my own custom rxjs operator that multiplies a stream of inputs by two. So 1:
const subscription = interval(1000).pipe(map(x => x * 2))
.subscribe(console.log);
setTimeout(() => subscription.unsubscribe(), 5000);
and 2:
const subscription = timesTwo(interval(1000))
.subscribe(console.log);
setTimeout(() => subscription.unsubscribe(), 5000);
and 3:
const subscription = interval(1000).pipe(timesTwoPipeable())
.subscribe(console.log);
setTimeout(() => subscription.unsubscribe(), 5000);
All have identical outputs to the console, but 2 and 3 both subscribe to the interval stream and then do not unsubscribe to it. So the second two quietly create a memory leak. You could test this yourself by changing interval(1000) to interval(1000).pipe(tap(_ => console.log("Still Alive"))) in all three examples.
All the built-in RxJS operators clean up after themselves. If you build your own, be sure to do the same!
Something I noticed in your question is that you tried to unsubscribe to an observable. I'm surprised that didn't create an error.
My inderstanding is that:
a$.subscribe();
a$.unsubscribe();
should be:
const sub = a$.subscribe();
sub.unsubscribe();