How to ignore new values in Observer during execution - rxjs

I have some Subject. And one Observer subscribed to it. How to omit all Observer invocations if it is already processing one?
var subject = new Subject();
var observer = {
next: x => {
//... some long processing is here
console.log('Observer got a next value: ' + x)
}
};
subject.subscribe(observer);
subject.next(0);
subject.next(1);// <-- if 0 value is not processed in the observer then skip it
subject.next(2);// <-- if 0 value is not processed in the observer then skip it
I of cause can introduce some flag, set it in Observer before execution and clear it after. And apply filter operator, like this:
var subject = new Subject();
var flag = true;
var observer = {
next: x => {
flag = false;
//... some long processing is here
console.log('Observer got a next value: ' + x)
flag = true;
}
};
subject.filter(() => flag).subscribe(observer);
subject.next(0);
subject.next(1);// <-- if previous value is not processed in the observer then skip it
subject.next(2);// <-- if 0 value is not processed in the observer then skip it
But I believe that exists more elegant and efficient way to achieve that.

Use the exhaustMap operator instead of trying roll your own backpressure. It is designed to ignore new events while waiting for the current one to complete.
const clicks = fromEvent(document, 'click');
const result = clicks.pipe(
exhaustMap((ev) => interval(1000).pipe(take(5))),
);
result.subscribe(x => console.log(x));

Related

How to use rxjs buffer with takeWhile

I am working on webrtc. The application sends icecandidates to backend firestore server.
The problem is the call to signaling server is made multiple times as onicecandidate is triggered multiple time. I want collect all the icecandidates and make a single call to signaling server.
The idea is to buffer all the events untill iceGathering is finished. This below attempt does not work
this.pc = new RTCPeerConnection(iceServers);
const source: Observable<any> = fromEvent(this.pc, 'icecandidate');
const takeWhile$ = source
.pipe(
takeWhile(val=> val.currentTarget.iceGatheringState === 'gathering'
))
const buff = source.pipe(buffer(takeWhile$));
buff.subscribe(() => {
// this.pc.onicecandidate = onicecandidateCallback;
})
Method 1:
You are almost there.
The takeWhile$ takes values and emits them while condition is met. So in buff, whenever takeWhile$ emits a value, buff emits a buffer of icecandidate events.
So you only need to emit one value in takeWhile$.
So what you need is takeLast() operator to only emit the last value.
When you put takeLast(1) in takeWhile$, it only emits last value and in buff, last emitted value leads to creation of buffer of icecandidate events.
this.pc = new RTCPeerConnection(iceServers);
const source: Observable<any> = fromEvent(this.pc, "icecandidate");
const takeWhile$ = source.pipe(
takeWhile(val => val.currentTarget.iceGatheringState === "gathering"),
takeLast(1)
);
const buff = source.pipe(buffer(takeWhile$));
buff.subscribe((bufferValues) => {
// bufferValues has a buffer of icecandidate events
// this.pc.onicecandidate = onicecandidateCallback;
});
You'll have access to buffer of icecandidate events in the subscription as bufferValues in above code.
Method 2:
You can also use reduce operator to achieve same scenario
this.pc = new RTCPeerConnection(iceServers);
const source: Observable<any> = fromEvent(this.pc, "icecandidate");
const takeWhile$ = source.pipe(
takeWhile(val => val.currentTarget.iceGatheringState === "gathering"),
reduce((acc, val) => [...acc,val], [])
);
takeWhile$.subscribe((bufferValues) => {
// bufferValues has a buffer of icecandidate events
// this.pc.onicecandidate = onicecandidateCallback;
})

Merge main Observable stream with updates

I need to merge main stream with updates stream this way:
Main: ----A-------B-------------C---|--
Upd: -D-----E---------F---G--------|--
========================================
Result:----A--AE---B----BF--BG---C---|--
I.e., when Main emitted, the result should always be Main (or Main with empty Upd). When Upd was emitted without previous Main, it should be ignored. If Upd was emitted after Main, then they should be combined.
Consider this TypeScript code:
interface Item {
Id: number;
Data: string;
}
function mergeUpdates(main: Item[], upd: Item[]) {
if (!upd || upd.length === 0) {
return main;
}
const result = main;
// const result = {...main};
for (const updatedItem of upd) {
const srcIndex = result.findIndex(_ => _.Id === updatedItem.Id);
if (srcIndex >= 0) {
result[srcIndex] = updatedItem;
} else {
result.push(updatedItem);
}
}
return result;
}
const main$ = new Subject<Item[]>();
const upd$ = new Subject<Item[]>();
const result$ = combineLatest(main$, upd$).pipe( // combineLatest is wrong operator!
map(([main, upd]) => mergeUpdates(main, upd)));
$result.subscribe(r => console.log(r.map(_ => _.Data).join(',')));
main$.next([{Id:1, Data:'Data1'}, {Id:2, Data:'Data2'}]);
upd$.next([{Id:1, Data:'Updated1'}]);
upd$.next([{Id:1, Data:'Updated2'}]);
main$.next([{Id:1, Data:'Data1_Orig'}, {Id:2, Data:'Data2'}]);
// Expexted result:
// 'Data1,Data2'
// 'Updated1,Data2'
// 'Updated2,Data2'
// 'Data1_Orig,Data2'
The only solution I have in mind is to use 'combineLatest' and mark items in upd$ stream as processed, thus do not use it again when data from main$ emitted later. I believe this is not the best approach as it cause unwanted side effects.
Is there any better solution for this task?
Thank you in advance.
Here would be my approach:
main$.pipe(
switchMap(
mainValue => merge(
of(mainValue),
upd$.pipe(
map(updVal => mainValue + updVal)
)
)
)
)
switchMap - make sure the inner observable's emitted values will be combined with the latest outer value
merge(of(a), upd$.pipe()) - emit the main value first, then listen to any notifications upd$ emits and combine them with the current main value
If another outer value comes in, the inner subscriber will be unsubscribed, meaning that the upd subject won't have redundant subscribers.

RXJS take 1 if synchronous, else use defaultValue

I know activatedRoute is actually BehaviourSubject, but since it's exposed API is observable, I don't want to take chances.
activatedRoute.queryParams.pipe(
take(1),
).subscribe(query => { // { page?: '1' | '2' | '3'... }
this.currentPage = new BehaviourSubject(+(query.page) || 1);
// Other components actually change this
});
console.log(this.currentPage); // I need this to not be undefined!
Does a pipe resembling this exists? takeSynchonously(1, { page: 1 })
I have found the answer, it is a combination of takeUntil, timer and queueScheduler.
const { BehaviorSubject, ReplaySubject, Subject, timer, queueScheduler } = rxjs;
const { startWith, defaultIfEmpty, takeUntil, take } = rxjs.operators;
new Subject().pipe(
take(1),
takeUntil(timer(1, queueScheduler)),
defaultIfEmpty(0),
).subscribe(n => console.log('Subject', n));
new ReplaySubject(1).pipe(
take(1),
takeUntil(timer(1, queueScheduler)),
defaultIfEmpty(0),
).subscribe(n => console.log('ReplaySubject', n));
const rs = new ReplaySubject(1);
rs.next(1)
rs.pipe(
take(1),
takeUntil(timer(1, queueScheduler)),
defaultIfEmpty(0),
).subscribe(n => console.log('ReplaySubject (with value)', n));
new BehaviorSubject(5).pipe(
take(1),
takeUntil(timer(1, queueScheduler)),
defaultIfEmpty(0),
).subscribe(n => console.log('BehaviorSubject', n));
console.log('priority')
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.2/rxjs.umd.js"></script>
Note: This must be used in combination with ReplySubject or similar. Anything with initial value will be synchronous, but those without it will be processed in the next tick.

redux observable map not invoked

I have this code, and failing to understand why I am not getting inside the map function (where I have the comment "I AM NEVER GETTING TO THIS PART OF THE CODE"):
export const fiveCPMonitoringLoadEpic = (action$, store) =>
action$
.ofType(
FIVE_CP_MONITORING_ACTION_TYPES.LOAD_FIVE_CP_MONITORING_DATA_STARTED
)
.debounceTime(250)
.switchMap(action => {
const params = action.params;
const siteId = { params };
// getting site's EDC accounts (observable):
const siteEdcAccount$ = getSiteEDCAccountsObservable(params);
const result$ = siteEdcAccount$.map(edcResponse => {
// getting here - all good so far.
const edcAccount = edcResponse[0];
// creating another observable (from promise - nothing special)
const fiveCPMonitoringEvent$ = getFiveCPAndTransmissionEventsObservable(
{
...params,
edcAccountId: edcAccount.utilityAccountNumber
}
);
fiveCPMonitoringEvent$.subscribe(x => {
// this is working... I am getting to this part of the code
// --------------------------------------------------------
console.log(x);
console.log('I am getting this printed out as expected');
});
return fiveCPMonitoringEvent$.map(events => {
// I NEVER GET TO THIS PART!!!!!
// -----------------------------
console.log('----- forecast-----');
// according to response - request the prediction (from the event start time if ACTIVE event exists, or from current time if no active event)
const activeEvent = DrEventUtils.getActiveEvent(events);
if (activeEvent) {
// get event start time
const startTime = activeEvent.startTime;
// return getPredictionMeasurementsObservable({...params, startTime}
const predictions = getPredictionMock(startTime - 300);
return Observable.of(predictions).delay(Math.random() * 2000);
} else {
// return getPredictionMeasurementsObservable({...params}
const predictions = getPredictionMock(
DateUtils.getLocalDateInUtcSeconds(new Date().getTime())
);
return Observable.of(predictions).delay(Math.random() * 2000);
}
});
can someone please shed some light here?
why when using subscribe it is working, but when using map on the observable it is not?
isn't map suppose to be invoked every time the observable fires?
Thanks,
Jim.
Until you subscribe to your observable, it is cold and does not emit values. Once you subscribe to it, the map will be invoked. This is a feature of rxjs meant to avoid operations that make no change (= no cunsumer uses the values). There are numerous blog posts on the subject, search 'cold vs hot obserables' on google

RxJS Emitting Subscribe Twice

I have a RXJS function that will create an empty Observable, tap into the result and return that new observable. I want the observable to always run the tap so I noop subscribe (in the real case it might not ever be subscribed to).
function that() {
let obs = of({});
obs = obs.pipe(tap(() => console.log('here')))
obs.subscribe();
return obs;
}
const res = that();
res.subscribe(() => console.log('finished'))
If you run this code on StackBlitz, you will notice that here is fired twice. The output looks like this:
here
here
finished
I've tried several different approaches but I can't ever seem to get it to work where it doesn't emit twice.
You subscribe TWICE:
function that() {
let obs = of({});
obs = obs.pipe(tap(() => console.log('here')))
obs.subscribe(); // first subscription
return obs;
}
const res = that();
res.subscribe(() => console.log('finished')) // second subscription
This is the same observable you subscribe to, once in the function, then on the returned value.
Just don't subscribe in the function
function that() {
let obs = of({});
obs = obs.pipe(tap(() => console.log('here')))
return obs;
}
const res = that();
res.subscribe(() => console.log('finished')) // subscribe from here only
See the updated StackBlitz.
Is it just a case of only tapping only the inner subscription?
function that() {
let obs = of({});
obs.pipe(tap(() => console.log('here'))).subscribe();
return obs;
}
const res = that();
res.subscribe(() => console.log('finished'))

Resources