Reputation: 528
here what my scenario is i have 2 api's apiOne and apiTwo and when ever i call the apiOne is should give response and if the response is success then i have to send this repsonse to apiTwo as param then apiTwo will give another response in that i may get like "created" ,"in_progress" . here the issue is
How can i call the apitwo using interval for every 3 seconds until i get the response as "in_progress" and if i didnt get the response as like above then i need to poll the apiTwo till max 2 min and cancel the call. if i get the response as in_progress then i need to stop the interval or max 2 min cancel the interval or subcription.
I already wrote the code in nested way but it is not efficient .
below is my code
initiate() {
this.showProgress = true;
const data = {
id: this.id,
info: this.Values.info,
};
// First Api call
this.userServ.start(data).subscribe(res => {
this.Ids = res['Id'];
if (this.Ids) {
// Second Api call
this.Service.getStatus(this.Ids).subscribe(resp => {
if (resp) {
this.Status = res['Status'];
// if resp is In_Progress
if (this.Status === 'In_Progress') {
this.Start();
} else {
// if resp is not In_Progress then i get the response i am calling the api
this.intervalTimer = interval(3000).subscribe(x => {
this.Service.Status(this.Ids).subscribe(ress => {
this.Status = ress['Status'];
if (this.Status === 'In_Progress') {
this.delayStart();
this.intervalTimer.unsubscribe();
}
});
});
}
}
}, err => {
console.log(err);
});
}
}, error => {
console.log(error);
});
}
Upvotes: 2
Views: 677
Reputation: 496
I'd use expand
from rxjs, which will pass through the result of the source observable, but also let's you act according to the content of the result.
Also, avoid nesting calls to subscribe
whenever possible. Consider this example code for reference:
this.userServ.start(data).pipe(
// use switchMap to not have 'nested' subscribe-calls
switchMap((result) => {
if (result['Id']) {
// if there is an ID, ask for the status
return this.Service.getStatus(result['Id']).pipe(
// use the expand operator to do additional processing, if necessary
expand((response) => response['Status'] === 'In_Progress'
// if the status is 'In_Progress', don't repeat the API call
? EMPTY
// otherwise, re-run the API call
: this.Service.getStatus(result['Id']).pipe(
// don't re-run the query immediately, instead, wait for 3s
delay(3000)
)
),
// Stop processing when a condition is met, in this case, 60s pass
takeUntil(timer(60000).pipe(
tap(() => {
// handle the timeout here
})
))
);
} else {
// if there is no ID, complete the observable and do nothing
return EMPTY;
}
}),
/**
* Since expand doesn't filter anything away, we don't want results that
* don't have the status 'In_Progress' to go further down for processing
*/
filter((response) => response['Status'] === 'In_Progress')
).subscribe(
(response) => {
this.Start();
}, (error) => {
console.log(error)
}
);
Upvotes: 1
Reputation: 15098
You may consider using the below approach See Code on Stackblitz
id = 1;
Values = { info: true };
get data() { return { id: this.id,info: this.Values.info}}
showProgressSubject$ = new BehaviorSubject(true);
showProgressAction$ = this.showProgressSubject$.asObservable();
currentStatusSubject$ = new Subject<string>();
currentStatus$ = this.currentStatusSubject$.asObservable()
stoppedSubject$ = new Subject();
stopped$ = this.stoppedSubject$.asObservable();
startedSubject$ = new Subject();
started$ = this.startedSubject$.asObservable();
interval = 500; // Change to 3000 for 3s
maxTrialTime = 6000;// Change to 120000 for 2min
timer$ = timer(0, this.interval).pipe(
tap((i) => {
if(this.maxTrialTime/this.interval < i) { this.stoppedSubject$.next()}
}),
takeUntil(this.stopped$),
repeatWhen(() => this.started$)
)
apiOneCall$ = this.userServ.start(this.data);
apiTwoCall$ = this.apiOneCall$.pipe(
switchMap(({Id}) => Id ? this.Service.getStatus(Id): throwError('No Id')),
tap((res) => this.currentStatusSubject$.next(res)),
tap(res => console.log({res})),
tap((res) => {if(res === 'created') {this.stoppedSubject$.next()}})
)
trialCallsToApiTwo$ = this.timer$.pipe(mergeMap(() => this.apiTwoCall$))
In your Html you can use the async pipe
Show Progress : {{ showProgressAction$ | async }} <br>
Timer: {{ timer$ | async }}<br>
Response: {{ trialCallsToApiTwo$ | async }}<br>
<button (click)="startedSubject$.next()">Start</button><br>
<button (click)="stoppedSubject$.next()">Stop</button><br>
We begin by setting up the properties id
, Values
and data
being a combination of the 2 values
id = 1;
Values = { info: true };
get data() { return { id: this.id,info: this.Values.info}}
We then create a Subject
to help with tracking of the progress of the operations. I am using BehaviorSubject to set the initial value of showing Progress to true.
We will use currentStatus$
to store whether current state is 'in_progress' or 'created'
stopped$
and started
will control our observable stream.
You may have a look at the below post What is the difference between Subject and BehaviorSubject?
showProgressSubject$ = new BehaviorSubject(true);
showProgressAction$ = this.showProgressSubject$.asObservable();
currentStatus$ = this.currentStatusSubject$.asObservable()
stoppedSubject$ = new Subject();
stopped$ = this.stoppedSubject$.asObservable();
startedSubject$ = new Subject();
started$ = this.startedSubject$.asObservable();
Next we define interval = 500; // Change to 3000 for 3s
and maxTrialTime = 6000;// Change to 120000 for 2min
We then define a timer$
observable using the timer
operator. The operator is used to generate a stream of values at regular interval
We set the delay to 0
and the interval to interval
property we had earlier created
We then tap
into the observable stream. The tap
operator allows us perform an operation without changing the observable stream
In our tap operator, we check whether the maximum time has been reached and if it has we call the next function on stoppedSubject$
. We pipe our stream to takeUntil(this.stopped$)
to stop the stream and repeatWhen(() => this.started$)
to restart the stream
timer$ = timer(0, this.interval).pipe(
tap((i) => {
if(this.maxTrialTime/this.interval < i) { this.stoppedSubject$.next()}
}),
takeUntil(this.stopped$),
repeatWhen(() => this.started$)
)
The Remaining part is to make a call to the apis
We will use switchMap
to combine the two observables. switchMap
will cancel any earlier request if a new request is made. If this is not your desired behaviour you may consider exhaustMap
or the mergeMap
operators
From the result of apiOneCall$
if no id, we use the throwError
operator to indicate an error otherwise we return a call to apiTwo
We tap into the result of apiTwoCall$
and call the next function on currentStatusSubject$
passing in the response. This sets the value of currentStatus$
to the result of the response
The line tap((res) => {if(res === 'created') {this.stoppedSubject$.next()}})
taps into the result of apiTwoCall$
and if it is 'created' it stops the timer
apiOneCall$ = this.userServ.start(this.data);
apiTwoCall$ = this.apiOneCall$.pipe(
switchMap(({Id}) => Id ? this.Service.getStatus(Id): throwError('No Id')),
tap((res) => this.currentStatusSubject$.next(res)),
tap(res => console.log({res})),
tap((res) => {if(res === 'created') {this.stoppedSubject$.next()}})
)
Now we finally combine the timer$
and apiTwoCall$
with mergeMap
operator trialCallsToApiTwo$ = this.timer$.pipe(mergeMap(() => this.apiTwoCall$))
In Our HTML we can then use the async
pipe to avoid worrying about unsubscribing
{{ trialCallsToApiTwo$ | async }}
Upvotes: 2