Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cancellable NGRX service polling in a Effect

How does one poll a service with an effect that stops polling until the returned value of service meets a condition OR the total duration exceeds a timeout threshold?

For example: A backend system is generating a resource, a frontend application can check whether that resource is available by calling a REST api call which returns a boolean. In my NGRX application I would like to poll every 200 ms this api call until this api call returns the boolean true OR the total polling duration exeeds the threshold of 10000 ms.

The following code samples shows a polling mechanism, however, this polling cannot be cancelled nor has a timeout. How is this done?

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/timer';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/switchMap';

@Effect()
pollEffect$: Observable<Action> = this.actions$
  .ofType(tasksActions.ActionTypes.START_POLLING)
  .switchMap(() => Observable.timer(0, 200)
    .switchMap(() => this.myBackendService.getAvailability().map(response => 
       return taskActions.ActionTypes.UpdateValue(response))
    )
  );
like image 924
JasperJ Avatar asked Jan 08 '18 14:01

JasperJ


2 Answers

Had exactly same issue and I could not find answer from internet. It took me some time to make it work. Here is what I did. I noticed where I put 'takeUntil(this.pollingUntil$)' matters, I put it on top since I needed to update store with latest data. If I put it on bottom(commented line), then polling was canceled and I could not update store with latest data.

private readonly pollingIntervalMs = 5000;
private readonly maxPollingMs = 600000; // 10 sec

private pollingUntil$: Subject<boolean> = new Subject<boolean>();

@Effect()
pollDb$: Observable<Action> = this.actions$.pipe(
  ofType(infraActions.InfraActionTypes.PollDb),
  switchMap(pollAction => interval(this.pollingIntervalMs).pipe(
    takeUntil(timer(this.maxPollingMs)),
    takeUntil(this.pollingUntil$),
    mapTo(pollAction),
    switchMap(
      (action: infraActions.PollDb) => this.dbService.get().pipe(
        map((dbInfo: DbInfo) => {
          if (meet condition) {
            this.pollingUntil$.next(true);
          }
          return dbInfo;
        })
      )
    ),
//    takeUntil(this.pollingUntil$),
    map((dbInfo: DbInfo) => {
      return new infraActions.PollDbSuccess(dbInfo);
    }),
    catchError(err => of(new infraActions.LoadDbFail(err)))
  )),
);
like image 116
chris cui Avatar answered Sep 30 '22 05:09

chris cui


You could try something like that:

@Effect()
pollEffect$: Observable<Action> = this.actions$
  .ofType(tasksActions.ActionTypes.START_POLLING)
  .switchMap(() => Observable.timer(0, 200)
    // stop the polling after a 10s timeout
    .takeUntil(Observable.of(true).delay(10000))
    .switchMap(() => this.myBackendService.getAvailability()
      .takeWhile(response => /* do your condition here */)
      .map(response => taskActions.ActionTypes.UpdateValue(response))
    )
  );

This way, the polling will stop either:
- after a 10s timeout
OR
- if your condition based on the response is true

EDIT 1:

According to your comment, indeed I think you should rather do:

@Effect()
pollEffect$: Observable<Action> = this.actions$
  .ofType(tasksActions.ActionTypes.START_POLLING)
  .switchMap(() => Observable.timer(0, 200)
    // stop the polling after a 10s timeout
    .takeUntil(Observable.of(true).delay(10000))
    .switchMap(() => this.myBackendService.getAvailability())
    .takeWhile(response => /* do your condition here */)
    .map(response => taskActions.ActionTypes.UpdateValue(response))
  );
like image 38
maxime1992 Avatar answered Sep 30 '22 04:09

maxime1992