Rxjs wait for previous interval request execution before proceeding - javascript

Use case: call an endpoint every 3-minutes that would update a status of a certain service over the application.
my current code:
interval(180000)
.subscribe(() => this.doRequest
.pipe(catchError(() => {
this.applicationFlag = false;
return EMPTY;
}))
.subscribe(result => this.applicationFlag = result));
My current problem is that sometimes the previous interval request has not been completed yet but the next interval request was also doing a request.
Is there a way to flag to wait for previous or don't execute the interval when the previous request is not completed yet?

When you have one subscribe inside another subscribe there's no way the outer chain can be notified that the inner chain has completed. You have to restructure your chain and use operators such as concatMap, mergeMap or switchMap. For example like the following:
interval(180000)
.pipe(
concatMap(() => this.doRequest.pipe(
catchError(() => {
this.applicationFlag = false;
return EMPTY;
}),
),
)
.subscribe();

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.

Reset RxJS concatMap without completing the outter Subject

I'm using a concatMap to handle multiple requests to an API, where I want each request batch to be completed before the next batch is processed. The concatMap works as expected when triggering the flow with callSubject.next(requestData)
The problem: for certain types of requestData I want to cancel any in-flight http calls, and reset the concatMap. Cancelling the httpClient calls that are occurring within the getAll function is handy enough (I have a takeUntil that does that - not shown), but the concatMap may still have a number of queued up requests that will then be processed.
Is there a way to reset the concatMap without completing the callSubject Subject?
Note: if I trigger unsubscribeCallSubject$.next() this clears the concatmap, but also completes the callSubject, which means it can no longer be used with callSubject.next(reqData)
// callSubject is a Subject which can be triggered multiple times
callSubject
.pipe(
concatMap((req) => {
// getAll makes multiple httpClient calls in sequence
return getAll(req).pipe(
catchError((err) => {
// prevent callSubject completing on http error
return of(err);
})
);
}),
takeUntil(unsubscribeCallSubject$)
)
.subscribe(
(v) => log("callSubject: next handler", v),
(e) => log("callSubject: error", e),
() => log("callSubject: complete")
);
If I understand the problem right, you could try an approach which uses switchMap any time unsubscribeCallSubject$ emits.
The code would look something like this
unsubscribeCallSubject$.pipe(
// use startWith to start the stream with something
startWith('anything'),
// switchMap any time unsubscribeCallSubject$ emits, which will unsubscribe
// any Observable within the following concatMap
switchMap(() => callSubject$),
// concatMap as in your example
concatMap((req) => {
// getAll makes multiple httpClient calls in sequence
return getAll(req).pipe(
catchError((err) => {
// prevent callSubject completing on http error
return of(err);
})
);
}),
)
.subscribe(
(v) => log("callSubject: next handler", v),
(e) => log("callSubject: error", e),
() => log("callSubject: complete")
);
To be honest I have not tested this approach and so I am not sure whether it solves your problem, but if I have understood your problem right, this could work.

pooling using Rx JS until all data is processed

What's the best way to write this short pooling routine using rx.js
1. call the function this.dataService.getRowsByAccountId(id) to return Observable<Row[]> from back-end
2. send the received data to this function this.refreshGrid(data);
3. if one of items in the data meet this criteria r.stillProcessing==true
4. then wait 2 seconds and start again from step-1
5. if another call was made to this routine and there is a pending timer scheduled. Don't schedule another one because i don't want multiple timers running.
I think the best solution would be using retryWhen
I don't know if this will work out of the box with your code, but according to your comment try to tweak this.
this.dataService.getRowsByAccountId(id)
.pipe(
tap((data: Row[]) => this.refreshGrid(data)),
map((data: Row[]) => {
if (data.some((r: Row) => r.stillProcessing === true) {
//error will be picked up by retryWhen
throw r.Name; //(or something else)
}
return r;
}),
retryWhen(errors =>
errors.pipe(
//log error message
tap((r: Row) => console.log(`Row ${r} is still processing!`)),
//restart in 2 seconds
delayWhen(() => timer(2000))
)
).subscribe();
);

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

How can I achieve a shareReplay with reconnection?

In the following code, I create a simple observable that produces one value and then complete. Then I share that observable replaying the last item and suscribe 3 times. The first right after, the second one before the value is produced and the third time after value is produced and the observable has completed.
let i = 0;
let obs$ = Rx.Observable.create(obs => {
console.log('Creating observable');
i++;
setTimeout(() => {
obs.onNext(i);
obs.onCompleted();
}, 2000);
}).shareReplay(1);
obs$.subscribe(
data => console.log(`s1: data = ${data}`),
() => {},
() => console.log('finish s1')
);
setTimeout( () => {
obs$.subscribe(
data => console.log(`s2: data = ${data}`),
() => {},
() => console.log('finish s2')
);
}, 1000);
setTimeout( () => {
obs$.subscribe(
data => console.log(`s3: data = ${data}`),
() => {},
() => console.log('finish s3')
);
}, 6000);
You can execute this on jsbin
This results in the following marble diagram
Actual
s1: -----1$
s2: \--1$
s3: \1$
But I would expect
Expected
s1: -----1$
s2: \--1$
s3: \----2$
I can understand why someone would like to have the first behaviour, but my reasoning is that, unlike this example, where I'm returning a number, I could be returning an object susceptible to unsubscribe behaviour, for example a database connection. If the above marble diagram represents a database connection, where in the dispose method I call a db.close(), on the third subscription I would have an exception, because I'm receiving as value a database handler that was released. (because when the second subscription finished refCount = 0 and the source is disposed).
Also another weird thing this example has, is that even it's resolving with
the first value and completing just after, its subscribing to the source twice (as you can see by the duplicated "Creating observable")
I know this github issue talks about this but what I'm missing is:
How can achieve (both in RxJs4 and 5) a shared observable that can replay the last item if the source observable hasn't completed, and if its done (refCount = 0), recreate the observable.
In RxJs5 I think the share method solves the reconnecting part of my problem, but not the sharing part.
In RxJs4 I'm clueless
If possible I would like to solve this using existing operators or subjects. My intuition tells me I would have to create a different Subject with such logic, but I'm not quite there yet.
A bit on shareReplay:
shareReplay keeps the same underlying ReplaySubject instance for the rest of the lifetime of the returned observable.
Once ReplaySubject completes, you can't put any more values into it, but it will still replay. So...
You subscribe to the observable the first time and the timeout starts. This increments i from 0 to 1.
You subscribe to the observable the second time and the timeout is already going.
The timeout callback fires and sends out onNext(i), then onCompleted().
onCompleted() signal completes the ReplaySubject inside the shareReplay, meaning that from now on, that shared observable will simply replay the value it has (which is 1) and complete.
A bit on shared observables in general:
Another, separate issue is that since you shared the observable, it's only ever going to call the subscriber function one time. That means that i will only ever be incremented one time. So even if you didn't onCompleted and kill your underlying ReplaySubject, you're going to end up not incrementing it to 2.
This isn't RxJS 5
A quick way to tell is onNext vs next. You're currently using RxJS 4 in your example, but you've tagged this with RxJS 5, and you've sighted an issue in RxJS 5. RxJS 5 is beta and a new version that is a complete rewrite of RxJS 4. The API changes were done mostly to match the es-observable proposal which is currently at stage 1
Updated example
I've updated your example to give you your expected results
Basically, you want to use a shared version of the observable for the first two calls, and the original observable for the third one.
let i = 0;
let obs$ = Rx.Observable.create(obs => {
console.log('Creating observable');
i++;
setTimeout(() => {
obs.onNext(i);
obs.onCompleted();
}, 2000);
})
let shared$ = obs$.shareReplay(1);
shared$.subscribe(
data => console.log(`s1: data = ${data}`),
() => {},
() => console.log('finish s1')
);
setTimeout( () => {
shared$.subscribe(
data => console.log(`s2: data = ${data}`),
() => {},
() => console.log('finish s2')
);
}, 1000);
setTimeout( () => {
obs$.subscribe(
data => console.log(`s3: data = ${data}`),
() => {},
() => console.log('finish s3')
);
}, 6000);
Unrelated
Also, protip: be sure to return a cancellation semantic for your custom observable that calls clearTimeout.

Categories

Resources