I have a Launch method, which takes some parameters and launches an engine. When the engine starts, it returns the instance name. With this instance name, I want to query another service periodically, say every 2 seconds to know if the status changed to "Succeeded" or "Failed" I did a do while loop inside the first subscription, but it is not working as expected.
instanceStatus: string = "Initialized";
instanceName:string = "InstanceName";
Launch(sessionId: string, projectName: string, f: string[]) {
this.service.Launch(sessionId, projectName, this.f)
.pipe(first())
.subscribe(
instanceName => {
localStorage.setItem('instanceName', instanceName);
this.instanceName = instanceName;
setTimeout(() => {
do {
this.service.getEngineStatus(this.instanceName)
.pipe(first())
.subscribe(
status => {
this.instanceStatus = status;
console.log(status);
console.log(this.instanceStatus);
this.loadingService.showSpinner({ text: 'Modeling is running...' });
if (this.instanceStatus === "Succeeded") {
this.messageService.add({ severity: Severity.Success, summary: 'Fault modeling completed', detail: 'Via MessageService' });
this.messageService.clear();
}
}
);
} while (this.instanceStatus !== "Succeeded")
}, 2000);
}
);
}
getEngineStatus(instanceName:string): Observable<string> {
this.serviceUrl = URL `?instance=` instanceName;
return this._http.get<string>(this.serviceUrl);
}
CodePudding user response:
Have you looked at the interval
creation operator? I believe this might be what you need to use. I gave it a shot using this operator and the code looks like this:
Launch(sessionId: string, projectName: string, f: string[]) {
this.service
.Launch(sessionId, projectName, this.f)
.pipe(
first(),
tap((instanceName) => {
localStorage.setItem('instanceName', instanceName);
this.instanceName = instanceName;
}),
switchMap(() => interval(2000)),
takeWhile(() => !['Succeeded', 'Failed'].contains(this.instanceStatus)),
tap(() => {
this.loadingService.showSpinner({
text: 'Modeling is running...',
});
}),
switchMap(() => this.service.getEngineStatus(this.instanceName))
)
.subscribe((status) => {
this.instanceStatus = status;
this.loadingService.hideSpinner();
console.log(status);
console.log(this.instanceStatus);
if (this.instanceStatus === 'Succeeded') {
this.messageService.add({
severity: Severity.Success,
summary: 'Fault modeling completed',
detail: 'Via MessageService',
});
this.messageService.clear();
}
});
}
CodePudding user response:
Avoid nested subscriptions. Instead try to use higher order mapping operators like
switchMap
.Instead of using JS statements like
for
orwhile
, using RxJS functions likeinterval
ortimer
for polling.Use
tap
operator to do side effects like pushing data to local storage.
import { Observable, timer } from 'rxjs';
import { tap, finalize, switchMap, takeWhile } from 'rxjs/operators';
POLL_INTERVAL = 2000;
instanceStatus: string = "Initialized";
instanceName:string = "InstanceName";
this.Launch(sample, sample, sample).pipe(
switchMap((instanceName: any) =>
timer(0, POLL_INTERVAL).pipe( // <-- start immediately and poll every 'n' secs
switchMap(() => this.service.getEngineStatus(instanceName)),
takeWhile((instanceStatus: any) => instanceStatus === 'Succeeded'), // <-- stop poll when status !== 'Succeeded'
finalize(() => { // <-- run when polling stops
this.messageService.add({
severity: Severity.Success,
summary: 'Fault modeling completed',
detail: 'Via MessageService'
});
this.messageService.clear();
})
)
).subscribe({
next: (instanceStatus: any) => this.instanceStatus = instanceStatus
});
)
Launch(sessionId: string, projectName: string, f: string[]): Observable<any> {
return this.service.Launch(sessionId, projectName, this.f).pipe(
first(),
tap((instanceName: any) => {
localStorage.setItem('instanceName', instanceName);
this.instanceName = instanceName;
})
);
}
Update (Thanks to @Liam):
do
is nowtap
operator.- The
.subscribe()
must be at the last level after thepipe()
function. It was erroneously inside it. Please try the code again.
CodePudding user response:
I suspect, from what I can see, that something like this would be part of the way toward an answer for you.
The key here is that the timer emits a value every 2 seconds while status !== "Succeeded"
. Once status === "Succeeded"
the true
for the second argument in takeWhile
will allow the final emission before it closes the timer (No more checks every 2 seconds).
The rest follows from that.
launch(sessionId: string, projectName: string, f: string[]): void {
this.service.launch(sessionId, projectName, f).pipe(
first(),
tap(instanceName => {
localStorage.setItem('instanceName', instanceName);
this.instanceName = instanceName;
}),
switchMap((instanceName:string) => timer(0,2000).pipe(
exhaustMap(_ => this.service.getEngineStatus(instanceName).pipe(
first()
)),
takeWhile((status:string) => status !== "Succeeded", true),
map(status => ({instanceName, status}))
))
).subscribe(({instanceName, status}) => {
this.loadingService.showSpinner({ text: 'Modeling is running...' });
if (status === "Succeeded") {
this.messageService.add({
severity: Severity.Success,
summary: 'Fault modeling completed',
detail: 'Via MessageService'
});
this.messageService.clear();
}
});
}
Update
Here is some code that does what you're after but is entirely self-contained. This isn't based on your code (since I can't run/test code I don't have access to).
The timers in the service simulate some delay between requesting and receiving a response.
type Status = "Succeeded" | "Other"
interface Service {
// We'll pretend we can't query privateStatus directly, for example
// it could be data on a server that you can only access via getStatus
privateStatus: Status,
launch: (sessionId: string) => Observable<string>,
getStatus: (instanceName: string) => Observable<Status>
}
class ArbiratryClass {
service: Service;
readonly PING_INTERVAL = 2000;
constructor(){
this.service = {
privateStatus: "Other",
launch: (sessionId: string) => timer(8000).pipe(
mapTo(""),
tap(status => this.service.privateStatus = "Succeeded"),
filter(_ => false),
startWith(`Hello ${sessionId}`)
),
getStatus: _ => timer(500).pipe(mapTo(this.service.privateStatus))
}
}
arbiratryInit(sessionId: string) {
// Launch session, then query the status every ping_Interval
// and print the result to the console.
this.service.launch(sessionId).pipe(
switchMap(instanceName => timer(0,this.PING_INTERVAL).pipe(
exhaustMap(_ => this.service.getStatus(instanceName)),
takeWhile(status => status !== "Succeeded", true),
map(status => ({instanceName, status}))
))
).subscribe(console.log);
}
}
new ArbiratryClass().arbiratryInit("ABCD_1234");
The output to the console when I run this:
{ instanceName: 'Hello ABCD_1234', status: 'Other' }
{ instanceName: 'Hello ABCD_1234', status: 'Other' }
{ instanceName: 'Hello ABCD_1234', status: 'Other' }
{ instanceName: 'Hello ABCD_1234', status: 'Other' }
{ instanceName: 'Hello ABCD_1234', status: 'Succeeded' }