RxJS - retry or reset - rxjs

Let's say I have a sequence like this:
Rx.Observable
.interval(1000)
.subscribe(data => {console.log(data)})
With operators, how can I 'restart' the sequence, meaning unsubscribe and resubscribe.
The real scenario is that the sequence is a socket stream, upon certain conditions we need to unsubscribe and resubscribe, kind of like the retryWhen(errors) works, but not with errors...would ideally be something like...retryWhen(bool:Subject).

I'd do it using switchMap() because it automatically unsubscribes from the old Observable and subscribes to the new one. In this case we'll use only .switchMap(() => source):
const subject = new Subject();
const source = Observable.create(obs => {
console.log('Observable.create');
obs.next(42);
});
subject.switchMap(() => source)
.subscribe(v => console.log('next:', v));
setTimeout(() => subject.next(), 1000);
setTimeout(() => subject.next(), 5000);
This prints the following:
Observable.create
next: 42
Observable.create
next: 42
Just instead of source you'll have your WebSocket source (or whatever you have).

Related

Reset Rx Js fromEvent observable after subscribe

I have mousemove event:
fromEvent(document, 'mousemove').pipe(
filter(e => e.target !== this.formElement.nativeElement),
filter(_ => this.form.valid),
take(1),
mergeMap((): any => this.confirm()),
).subscribe();
confirm() {
this.snack.open('sure', 'yes').onAction()
.subscribe(_ => {
this.createNoteService.create();
})
}
After confirming the snack button I want the mouse event to start working again.
Is it possible? Thanks!
The reason this only works for you the first time, is because you have take(1) which ends the observable after one value is received.
Also, your confirm() method should return observable; don't subscribe inside the function; mergeMap will automatically subscribe / unsubscribe for you.
However, instead of mergeMap, you can use exhaustMap. This will only allow a single inner subscription at a time. It will ignore any incoming emissions until its current source completes:
fromEvent(document, 'mousemove').pipe(
filter(e => e.target !== formElement.nativeElement),
filter(_ => form.valid),
exhaustMap(() => confirm()),
).subscribe();
function confirm() {
return snack.open('sure', 'yes').onAction().pipe(
tap(() => this.createNoteService.create())
);
}

how to unsubscribe a RXJS subscription inside the subscribe method?

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 :).

Can a subscription remain subscribed if returning a still subscribed observable from a switchmap

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();

RxJS: forkJoin mergeMap

I'm trying to make multiple http requests and get returned data in one object.
const pagesToFetch = [2,3]
const request$ = forkJoin(
from(pagesToFetch)
.pipe(
mergeMap(page => this.mockRemoteData(page)),
)
)
mockRemoteData() return a simple Promise.
After first Observable emits (the once created from first entry of pagesToFetch the request$ is completed, second value in not included. How can I fix this?
You can turn each value in pagesToFetch into an Observable and then wait until all of them complete:
const observables = pagesToFetch.map(page => this.mockRemoteData(page));
forkJoin(observables)
.subscribe(...);
Or in case it's not that simple and you need pagesToFetch to be an Observable to collect urls first you could use for example this:
from(pagesToFetch)
.pipe(
toArray(),
mergeMap(pages => {
const observables = pages.map(page => this.mockRemoteData(page));
return forkJoin(observables);
}),
)
.subscribe(...);
Try the below sample format...
Observable.forkJoin(
URL 1,
URL 2
).subscribe((responses) => {
console.log(responses[0]);
console.log(responses[1]);
},
error => {console.log(error)}
);

redux-observable epic that doesn't send any new actions

Might be that I'm a noob and not fully understanding how this stuff should work yet, but I have an epic in redux-observable in which I want to use as a way to create a promise which will dispatch an action and wait for a different action before resolving. I've got it working by mapping the action to '__IGNORE__' but I really don't want to do that. Is there any way to just have an epic handle an action, but not pass anything else on?
Here's my code:
export const waitFor = (type, action) => new Promise((resolve, reject) => {
const waitForResult = action$ => action$.ofType(type).do(() => resolve()).mapTo({type: "___IGNORE___"});
registerEpic(waitForResult);
action();
});
You can throw away any next'd values from an observable chain by using the .ignoreElements() RxJS operator
action$.ofType(type)
.do(() => resolve())
.ignoreElements();
Another way of doing this (no more right or wrong) is to create an anonymous Observable that just subscribes.
const waitForResultEpic = action$ => new Observable(observer =>
action$.ofType(type)
.subscribe(() => resolve())
);
This is implicitly returning the subscription we create, so that it's attached to the lifecycle of our rootEpic as well. Because we never call observer.next(), this epic never emits any values; just like ignoreElements().
Although you didn't ask, you may eventually notice that your epic will run forever, listening for that incoming action matching the type variable. This may not be not what you want, if you want to match once then complete.
You can accomplish that using .take(1) operator.
const waitForResult = action$ =>
action$.ofType(type)
.take(1)
.do(() => resolve())
.ignoreElements();
Or
const waitForResult = action$ => new Observable(observer =>
action$.ofType(type)
.take(1)
.subscribe({
next: () => resolve(),
error: err => observer.error(err),
complete: () => observer.complete()
})
);
This will only match once per the life of the application--once received it will never do it again.

Resources