Home > database >  Query a web api periodically with Angular observable and subscribe
Query a web api periodically with Angular observable and subscribe

Time:11-09

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:

  1. Avoid nested subscriptions. Instead try to use higher order mapping operators like switchMap.

  2. Instead of using JS statements like for or while, using RxJS functions like interval or timer for polling.

  3. 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):

  1. do is now tap operator.
  2. The .subscribe() must be at the last level after the pipe() 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' }
  • Related