Cancel subscription, when subscribing two observables to each other - javascript

I have two observables, that never complete (they are event handlers). Every time that observable A emits, I want to discard all emissions of B, wait for B to emit a value, and do something with A's emission; ignore further B emissions. If A emits a second time, while waiting for B, I want to cancel waiting.
Currently I have the following code:
obsA$.subscribe(value => {
obsB$.pipe(take(1)).subscribe(_ => {
if (value) {
// do stuff with value
}
});
});
This does not cover the case when: A emits once, then A emits again, and then B emits a value, the 1st subscription to B should be cancelled, and only the second subscription should execute.
How should I approach this issue? Is there some better / clearer way to write this?
ObservableA is a navigation event (navigation to a new address), and ObsB is from an animation event, that happens after navigation. When navigating to a new address, I want to wait for the animation to complete, and then do something.

It looks like you're describing switchMap that always subscribes to the Observable returned from its callback and unsubscribes from the previous one.
obsA$
.pipe(
switchMap(v => obsB$.pipe(mapTo(v))), // pass through only the original emission from obsA$
)
.subscribe(resultFromA => ...);

Related

RxJS waiting for array elements to complete not working as intended

I'm expecting the following RxJS behavior: for every element in the source array, execute an action (the part commented out) which needs to be awaited to complete, and only then fetch next element in source array, wait again and so on.
But the behavior I get instead, is all elements in the source array are fetched at the same time, then after the delay they are retried all again etc.
import { from, defer, delay, repeat, tap } from 'rxjs';
const source$ = from([1, 2, 3])
const actions$ = source$.pipe(
tap((t) => console.log(t))
// ... action that takes long and needs to be waited for, before going to the next element in source$
)
const timedExecution$ = defer(() => actions$).pipe(
delay(3000),
repeat(3)
)
timedExecution$.subscribe();
I also tried another way, with timer:
import { from, tap, timer } from 'rxjs';
const source$ = from([1, 2, 3])
const actions$ = source$.pipe(
() => timer(0, 3000),
tap((t) => console.log(t))
// actionThatTakesLong() action that takes long and needs to be waited for, before going to the next element in source$
)
actions$.subscribe();
Here, it emits one at a time, but sometimes the actionThatTakesLong() takes longer than the arbitrary 3000 MS value of the timer, and i need it to wait until its done, instead of a hardcoded value of waiting.
Thanks for any hints in advance
Your source Observable is from() which is a synchronous Observable that emits array items one after another immediately on subscription. It doesn't (and can't) care what happens with the values in the chain.
delay() will take each value and delay it by a certain time but it doesn't (and can't) care whether the previous values have reached your observer. It just takes each value and delays it by 3s without waiting for the previous delay to complete so in your case it appears like all values were emitted at the same time.
What you want to do instead is adding concatMap() operator that will wait until the nested delayed Observable completes:
from([1, 2, 3])
.pipe(
concatMap(value => of(value).pipe(delay(3000))),
)
.subscribe(...);
FYI, the second option you are mentioning does something very different than you think:
const actions$ = source$.pipe(
() => timer(0, 3000),
tap(() => ...),
);
This is actually replacing the source Observable from() with a different Observable timer(0, 3000). You're basically using approach used for creating custom operators https://rxjs.dev/guide/operators#creating-new-operators-from-scratch.

Wait till all Observables are completed

I have few Observables like this one in my code.
this.server.doRequest().subscribe(response => console.log(response)
error => console.log(error),
() => {
console.log('completed');
});
There could be any number of these Observables,
so I need to write a function that checks if each Observable is done otherwise waits till each is finished.
I'm assuming I can create an array push every new Observable there and when it's completed remove it by index. But is it good solution?
Where I want to use it. For example I have a page where user upload photos any amount asynchronously and then he press Finish button. Once he pressed Finish button I need to wait till ALL dynamically created Observables are completed.
you should use higher order observables for this, your exact use case will dictate the exact operator, but forkJoin seems a good candidate:
forkJoin(
this.server.doRequest1(),
this.server.doRequest2(),
this.server.doRequest3(),
this.server.doRequest4()
).subscribe(vals => console.log('all values', vals));
forkJoin won't emit till all innter observables have completed. making it the operator of choice for waiting for multiple observables to complete. You can also feed it an array of observables. There are multiple other operators that may fulfill your case too, such as concat, merge, combineLatest or a few others.
edit based on more details:
in the use case described in your update, you'll still want to use a higher order observable, but forkjoin is not what you want. you'll want to use a local subject to accomplish the goal as wanting to kick off each observable as it is selected and waiting for them all to be done complicates things a little (but not too much):
suppose you had a template like:
<button (click)="addPhoto()">Add Photo</button>
<button (click)="finish()">Finish</button>
where the add photo button gets the users photo and all that, and finish is your completion, you could have a component like this:
private addPhoto$ = new Subject();
constructor() {
this.addPhoto$.pipe(
mergeMap(() => this.uploadPhoto()),
).subscribe(
(resp) => console.log('resp', resp),
(err) => console.log('err', err),
() => console.log('complete')
);
}
private uploadPhoto() {
// stub to simulate upload
return timer(3000);
}
addPhoto() {
this.addPhoto$.next();
}
finish() {
this.addPhoto$.complete();
}
if you run this code, you'll see that the photo adds will emit in the subscribe handler as they complete, but complete will only fire once all the photo uploads have completed and the user has clicked finish.
here is a stackblitz demonstrating the functionality:
https://stackblitz.com/edit/angular-bsn6pz
I'd create a dictionary (in javascript that would be a JSON with observable names as boolean properties) where you push each observable on "create" and a method which should execute on completion of each observable, which will iterate through that dictionary and if all completed do something.
That will ensure parallelism and final execution after all completed.
var requests = {
doRequest1: false,
doRequest2: false,
doRequest3: false
};
var checkIfCAllCompleted = name => {
requests[name] = true;
for (var property in requests) {
if (object.hasOwnProperty(property)) {
if (!property) {
return;
}
}
}
// all properties are true - do something here
console.log("here");
}
this.server.doRequest1().then(() => checkIfCAllCompleted("doRequest1"));
this.server.doRequest2().then(() => checkIfCAllCompleted("doRequest2"));
this.server.doRequest3().then(() => checkIfCAllCompleted("doRequest3"));

Concat Array of Observables and have a single subscription output

I have several cases on my software where I have an array of observables and I need to execute them in order. Having the next subscription to happen only after the previous is complete.
So Im using the concat operator. It works great, however its subscription gets triggered every time one of the Observables gets completed, and I need to have it be triggered only after everything is complete.
concat(
of(1, 2, 3).pipe(delay(3000)),
// after 3s, the first observable will complete and subsquent observable subscribed with values emitted
of(4, 5, 6).pipe(delay(3000)),
)
// log: 1,2,3,4,5,6
.subscribe((v) => {
// Needs to be triggered once after everything is complete
console.log(v);
});
I need a way to pipe this observable so the subscription gets triggered only once after everything is complete, the value of the subscription is not important in this case, so it can be omitted.
If possible the values could be made available in a form of an array inside the subscription context.
Collect the values in an array with toArray.
import { toArray } from 'rxjs/operators';
concat(
of(1, 2, 3).pipe(delay(3000)),
of(4, 5, 6).pipe(delay(3000)),
).pipe(
toArray()
).subscribe(v => console.log(v)); // log: [1,2,3,4,5,6]
Or if you don't need the response use the complete callback like in #Willem's solution.
Pipe the results into a finalize():
Call a function when observable completes or errors
See https://www.learnrxjs.io/operators/utility/finalize.html
Subscribe to the complete event:
.subscribe({
complete: () => { ... }
})
Use forkJoin(), especially if you want the final values:
When all observables complete, emit the last emitted value from each.
https://www.learnrxjs.io/operators/combination/forkjoin.html

Process observable subscribe events synchronously

I'm looking for a way to process events from ReplaySubject.subscribe() in a synchronous fashion.
let onSomeEvent = new ReplaySubject();
onSomeEvent.subscribe(async (event) => {
return await this.saveEventToDb(event);
});
In this example, saveEventToDb() first checks the database whether an event with the same ID was already stored. If not, it stores it.
The problem is I need to account for duplicate events firing from the subject.
In this example, when 2 duplicate event fire back-to-back, both get added to the database because saveEventToDb() gets called twice immediately without waiting for the previous call to finish.
How can I queue these up using Rxjs?
The following worked to process the events synchronously:
onSomeEvent
.map(event => {
return Observable.defer(() => {
return this.saveEventToDb(event);
});
})
.concatAll()
.subscribe();
ConcatAll(): Collect observables and subscribe to next when previous completes.

How to prevent concurrent effect execution

I have an expensive calculation that is called by an effect. I now want to ensure, that this calculation is never called concurrently, i.e. if it is called a second time while the first call is still running, the second call should be ignored.
My approach to this problem was to create 2 actions: calculate and setLoading.
#Effect()
calculate$ = this.updates$
.whenAction(CALCULATE)
.flatMap(data => {
console.debug('LOADING', data.state.loading);
if (!data.state.loading) {
this.store.dispatch(Actions.setLoading(true));
await DO_THE_EXPENSIVE_CALCULATION();
this.store.dispatch(Actions.setLoading(false));
}
});
with Actions.setLoading obviously setting state.loading. However, if I start the calculation 2 times in a row:
store.dispatch(Actions.calculate());
store.dispatch(Actions.calculate());
the output is
LOADING false
LOADING false
and therefore, the expensive calculation is executed two times.
How can I prevent this?
You might see the LOADING false twice because the Action.setLoading have not been executed yet. This is very possible depending on the synchrony/asynchrony of dispatching and actions. Best is to not make hypothesis about that.
Generally speaking, if you want to limit a number of operations executing at the same time/execute only one operation at a time, there are a number of operators you could use in rxjs v4/v5:
flatMapWithMaxConcurrent|mergeMap : will subscribe to the parameterized maximum of observables concurrently, keep a buffer of the remaining observables to subscribe to, and subscribe to them when a slot becomes available. Hence there is no loss.
flatMapFirst|exhaustMap : will only subscribe to one observable at a given time. Observable which come while the current observable is being executed are lost. When the current observable is completed, new observables can be subscribed to.
concatMap : will subscribe only to one observable at a time. Will keep a buffer of remaining observables to subscribe to, and will do that in order when the current observable has completed. Hence there is no loss.
You can also review the following question : Batching using RxJS?
In conclusion, maybe something like this could work for you :
#Effect()
calculate$ = this.updates$
.whenAction(CALCULATE)
.exhaustMap(data => DO_THE_EXPENSIVE_CALCULATION())
;
I supposed here that the DO_THE_EXPENSIVE_CALCULATION() returns a promise (could also be an observable).

Categories

Resources