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();
Related
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 :).
Hello,
first of all, thank you for reading this. π
I want to handle scroll events stream and I want to react on scroll starts and ignore following burst of scroll events until stream considered inactive (time limit). So after delay I want repeat the same.
This is my solution so far:
import { fromEvent } from 'rxjs';
import { throttle, debounceTime } from 'rxjs/operators';
const stream = fromEvent(window, 'scroll');
const controllerStream = stream.pipe(debounceTime(500));
this.sub = stream
.pipe(
throttle(() => controllerStream, {
leading: true,
trailing: false,
})
)
.subscribe(() => {
// react on scroll-start events
});
Is there a better way?
I was considering operators like throttleTime, debounce, debounceTime... but I could not find the configuration matching my needs
Thank you ππ
While this solution looks a bit involved, it achieves the behavior you describe, and can be encapsulated cleanly in a custom operator.
import { of, merge, NEVER } from 'rxjs';
import { share, exhaustMap, debounceTime, takeUntil } from 'rxjs/operators';
const firstAfterInactiveFor = (ms) => (source) => {
// Multicast the source since we need to subscribe to it twice.
const sharedSource = source.pipe(share());
return sharedSource.pipe(
// Ignore source until we finish the observable returned from exhaustMap's
// callback
exhaustMap((firstEvent) =>
// Create an observable that emits only the initial scroll event, but never
// completes (on its own)
merge(of(firstEvent), NEVER).pipe(
// Complete the never-ending observable once the source is dormant for
// the specified duration. Once this happens, the next source event
// will be allowed through and the process will repeat.
takeUntil(sharedSource.pipe(debounceTime(ms)))
)
)
);
};
// This achieves the desired behavior.
stream.pipe(firstAfterInactiveFor(500))
I have made a third version, encapsulating my solution into custom operator based on #backtick answer. Is there a problem with this solution? Memory leak or something? I am not sure whether inner controllerStream will destroy properly or at all.
const firstAfterInactiveFor = (ms) => (source) => {
const controllerStream = source.pipe(debounceTime(ms));
return source
.pipe(
throttle(() => controllerStream, {
leading: true,
trailing: false
})
)
};
// This achieves the desired behavior.
stream
.pipe(
firstAfterInactiveFor(500)
)
.subscribe(() => {
console.log("scroll-start");
});
Here is codepen with comparison of all three:
https://codepen.io/luckylooke/pen/zYvEoyd
EDIT:
better example with logs and unsubscribe button
https://codepen.io/luckylooke/pen/XWmqQBg
Here is what I am trying to do - step #3 is what I am struggling with.
Set custom interval
trigger an http call (subscribe to it)
Once interval is finished, I want to repeat the above steps indefinitely (until a condition is met)
Steps #1 & #2 are working
setRenewalInterval() {
...
return this.timerSub = interval(CUSTOM_INTERVAL * 1000)
.pipe(
take(1),
map(x => {
if (this.isLoggedIn && !tokenHelper.isTokenExpired()) {
console.log("<Requesting Token Renew>")
this.getNewToken().subscribe(
x => {
console.log("<Token renewed successfully>");
},
err => {
console.log("<Token failed to renew>");
this.setLogOut();
},
() => console.log('<renewal observable completed>')
);
}
else {
console.log("<Not Logged in or Token expired; logging out>");
this.setLogOut();
}
})
)
.subscribe();
}
#3 repeat the above steps after the subscription is completed. Either call setRenewalInterval() or preferably do it inside the setRenewalInterval().
I have looked into rxjs repeat() and expand() but I can't get them to work with the above code. Any help is greatly appreciated, especially a testable/runnable code e.g. jsfiddle.
You're really close, interval(...) is already creating an emission indefinitely however take(1) is causing the entire observable to complete after the first emission.
If you remove the take(1) your code inside the map will be called at your interval forever.
map is a useful function when you want to take values emit in an observable and convert it to a new value synchronously, similar to how Array.map() works. Because you're working with observables inside this map we can use something like mergeMap() which will handle the subscription and piping through of values automatically.
setRenewalInterval() {
// ...
return this.timerSub = interval(CUSTOM_INTERVAL * 1000)
.pipe(
mergeMap(() => {
if (this.isLoggedIn && !tokenHelper.isTokenExpired()) {
return this.getNewToken();
} else {
return this.setLogOut();
}
})
)
.subscribe(x => {
// x is the result of every call to getNewToken() or setLogOut()
});
}
I have an observable stream set up as below. I have an interval that is polling every two seconds. I then switchMap that to make two dependent API calls (mocked here with 'of's). After, I want to use distinctUntilChanged to make sure the final object is different. The only thing is that distinctUntilChanged doesn't fire.
I'm assuming it has SOMETHING to do with the fact that we are creating new streams and therefore never collects two objects to compare, but I don't fully understand.
interval(2000).pipe(
switchMap(() => loadData()),
)
.subscribe(res => console.log(res)); // { name: 'test' } is printed every two seconds
function loadData() {
return of('API call').pipe(
mergeMap(numb => of({ name: 'test' })),
distinctUntilChanged((prev, cur) => {
console.log('CompareFn'); // This will never fire.
return JSON.stringify(prev) === JSON.stringify(cur)})
);
}
Stackblitz: https://stackblitz.com/edit/rxjs-ko6k3c?devtoolsheight=60
In this case, I would like there to only be a single value ever printed from the next handler as distinctUntilChanged should stop all values after the first.
Would appreciate an explanation as to why this isn't working as I would expect it to.
the problem is that your distinctUntilChanged is operating on the inner observable, not the outter... you need to do this
interval(2000).pipe(
switchMap(_ => loadData()),
distinctUntilChanged((prev, cur) => {
console.log('CompareFn');
return JSON.stringify(prev) === JSON.stringify(cur);
})
)
.subscribe(res => console.log(res));
function loadData() {
return of('API call').pipe(
mergeMap(numb => of({ name: 'test' }))
);
}
in your prior set up, only one value ever reached distinctUntilChanged as the interval switched into a new observable via switch map.
My scenario is a classic web page with a search form and a result list. I want to encapsulate the behavior of loading the results in an Observable.
Here's what I'm currently doing in TypeScript:
function loadResults(query): Observable<T[]> {}
const results = new Subject<ResultEvent<T[]>>();
const results: Observable<ResultEvent<T[]>> =
form.valueChanges
.distinctUntilChanged()
.do(() => results.next(ResultEvent.pending()))
.switchMap(query => loadResults(query))
.subscribe({
next: (data: T[]) => results.next(ResultEvent.present(data)),
error: err => results.next(ResultEvent.failed(err)),
});
The idea is that results always contains the current state of the search: either pending, present or failed. When the query changes, the result is set to pending, and when the service returns data, the result is set to present.
What I don't like about this solution is the explicit call to subscribe(). I'd rather have a simple Observable that can be subscribed an unsubscribed from (eg. in Angular with the async pipe), without creating an explicit subscribtion. The side-effects in do also seem rather hacky.
const results: Obserbable<ResultEvent<T[]>> =
form.valueChanges.distinctUntilChanged()
. /* here be dragons */;
Thanks for any advice and ideas!
I think you want something along these lines:
const results$ = form.valueChanges
// This is up to you, but with user input it might make sense to
// give it just a little bit of time before we hit the server since
// most user input will be more than a single character.
//.debounceTime(100)
.distinctUntilChanged()
// Using switchMap guarantees that the inner observable will be
// cancelled if the input changed while we are still waiting for
// a result. Newer is always better!
.switchMap(query => loadResults(query)
// If we get data, we use it.
.map(results => ResultEvent.present(results))
// We catch errors and turn them into a failure event.
.catch(err => Observable.of(ResultEvent.failed(err)))
// Whatever happens, first things first.
.startWith(ResultEvent.pending())
);
I would also think about adding a debounceTime in there, by the way.
Here's a snippet you can copy-paste into https://rxviz.com to see it in action (unfortunately their share link feature doesn't work anymore). Make sure to set the time window to something like 10 seconds.
const ResultEvent = {
pending: () => 'Pending',
failed: err => 'Error: ' + err,
present: data => 'Data: ' + data,
};
const loadResults = query => query === 2
? Rx.Observable.of(null).delay(500).switchMap(() => Rx.Observable.throw('Oops'))
: Rx.Observable.of(42).delay(500)
const input$ = Rx.Observable.timer(0, 2000).take(4);
input$.switchMap(query => loadResults(query)
.map(data => ResultEvent.present(data))
.catch(err => Rx.Observable.of(ResultEvent.failed(err)))
.startWith(ResultEvent.pending())
)