Programing

Angular NgRx - 처음 호출된 서비스 폴링을 계속하는 효과

c10106 2022. 3. 19. 10:39
반응형

Angular NgRx - 처음 호출된 서비스 폴링을 계속하는 효과

방금 NgRX를 추가한 응용 프로그램이 있는데, 이 응용 프로그램을 사용하여 폴링을 켜고 끄십시오.

샘플 개요

나는 좋은 접근인 것 같은 이 게시물을 따라갔다.여기에 간단한 예가 있는데, 코드의 대부분이app.effects.ts.

예시와 유사하게, 나는 효과가 있다.startPolling$stopPolling$그리고continuePolling$, 내가 새로운 것을 사용하고 있다는 것 빼면.createEffect공장 방식

또한, 나는 그 집을 옮겼다.delay(2000)상위의takeWhile() 된 콜에 문제가 생기면.catchError(err => of(appActions.getDataFail(err)))지연 없이 매우 빠른 연속 루프에 영향을 미칠 수 있다.

시작과 중지 버튼을 누르면 폴링 시작과 중지...

public start() {
    console.log('dispatching start');
    this.store.dispatch(appActions.startPolling());
  }

  public stop() {
    console.log('dispatching stop');
    this.store.dispatch(appActions.stopPolling());
  }

내 문제

콘솔 로그가 몇 개 있어서 무슨 일인지 알 수 있어.

시작 단추(처음만)를 클릭하면 투표 시작 모습이 보이고, 예상대로 계속된다.예를 들면 다음과 같은 것을 몇 번이고 볼 수 있다...

dispatching start
app effect started polling
app.service.getData
app effect continue polling
app.service.getData
app effect continue polling
app.service.getData
app effect continue polling

퍼펙트

그리고 내가 정거장에 도착했을 때, 나는 본다.

dispatching stop
app effect stop polling

또한 정확하다.

문제는 내가 다시 시작하려고 할 때야이제 시작 버튼을 다시 클릭하면 초기 폴링 효과만 보여...

dispatching start
app effect started polling
app.service.getData

그리고 암호가 더 이상 호출되지 않아, 그래서 여론조사가 없어.

이 효과가 왜 초 단위 시간을 유발하지 않는지 아는 사람 있어?나는 단지 이것이 왜 그런지 이해할 수 없다.

업데이트 1

은 한국이라는 이다.isPollingActive거짓으로 설정되고takeWhile(() => this.isPollingActive),"관측 가능한" 즉, 관측 가능한 것은 더 이상 활성화되지 않는다.continuePolling$그래서 다시 시작하지 않는거야?

이를 가정하여, 나는 두 개의 변수가 있는 경우, 하나는 폴링(예: 오프라인 모드에서 앱을 탐지하는 경우)을 "일시 중지"하고 다른 하나는 취소(사용자가 구성요소를 탐색할 때)를 시도했다.

이제 내 모든 효과는...

    @Injectable()
    export class AppEffects {
      private isPollingCancelled: boolean;
      private isPollingPaused: boolean;

      constructor(
        private actions$: Actions,
        private store: Store<AppState>,
        private appDataService: AppDataService
      ) { }

      public startPolling$ = createEffect(() => this.actions$.pipe(
        ofType(appActions.startPolling),
        tap(_ => console.log('app effect started polling')),
        tap(() => {
          this.isPollingCancelled = false;
          this.isPollingPaused = false;
        }),        
          mergeMap(() =>
            this.appDataService.getData()
              .pipe(                        
                switchMap(data => {              
                  return [appActions.getDataSuccess(data)
                  ];
                  }),
                catchError(err => of(appActions.getDataFail(err)))
              ))
        ));

         public pausePolling$ = createEffect(() => this.actions$.pipe(
            ofType(appActions.pausePolling),
            tap(_ => this.isPollingPaused = true),
            tap(_ => console.log('app effect pause polling')),       
         ));
      
      public cancelPolling$ = createEffect(() => this.actions$.pipe(
        ofType(appActions.cancelPolling),
        tap(_ => this.isPollingCancelled = true),
        tap(_ => console.log('app effect cancel polling')),
      ));

        public continuePolling$ = createEffect(() => this.actions$.pipe(
          ofType(appActions.getDataSuccess, appActions.getDataFail),    
          tap(data => console.log('app effect continue polling')),  
          takeWhile(() => !this.isPollingCancelled),    
          delay(3000),  
     
          mergeMap(() =>
            this.appDataService.getData()
              .pipe(   
                delay(3000),  
                tap(data => console.log('app effect continue polling - inner loop')),  
                takeWhile(() => !this.isPollingPaused), // check again incase this has been unset since delay 
                switchMap(data => {              
                  return [appActions.getDataSuccess(data)
                  ];
                  }),
                catchError(err => of(appActions.getDataFail(err)))
              ))
        ));    
    } 

와 같이 가 위와 은 을 운영하는 것을 않다.pause polling action그 효과는 끝이 없는 것 같아. 그리고 나는 작업 매니저를 통해 브라우저를 죽여야 해.

왜 이런 일이 일어나는지 알 수 없지만, 예전보다 해결책에서 더 멀어 보인다.

업데이트 2

나는 일시 중지 및 취소 효과로부터 어떠한 조치도 반환하지 않는다는 것을 알아차렸다.

그래서 나는 그것들을 업데이트했다 우리가 따르는...

 public pausePolling$ = createEffect(() => this.actions$.pipe(
    ofType(appActions.pausePolling),
    tap(_ => this.isPollingPaused = true),
    tap(_ => console.log('app effect pause polling')),
    map(_ => appActions.pausePollingSuccess())
  ));
  
  public cancelPolling$ = createEffect(() => this.actions$.pipe(
    ofType(appActions.cancelPolling),
    tap(_ => {
      this.isPollingCancelled = true;
      this.isPollingPaused = true;
    }),
    tap(_ => console.log('app effect cancel polling')),
    map(_ => appActions.cancelPollingSuccess())
  ));

이제 정지가 잘 되는 것 같지만, 내가 파견을 할 때론appActions.cancelPolling, 나는 다시 무한의 루프처럼 본다.app effect cancel polling콘솔에 로깅됩니다.

업데이트 3

나는 내가 왜 무한순환을 겪는지 그리고 그것을 어떻게 멈추어야 하는지 알아냈다.여기 도코에 따르면, 나는 그 이름을 추가할 수 있다.dispatch:false...

    public cancelPolling$ = createEffect(() => this.actions$.pipe(
        ofType(appActions.cancelPolling),
        tap(_ => {
          this.isPollingCancelled = true;
          this.isPollingPaused = true;
        }),
        tap(_ => console.log('app effect cancel polling')),
      ), { dispatch: false }); // <------ add this

이게 내 무한한 고리를 고치는 것 같아

지금 나의 유일한 임무는 투표의 시작, 중지 및 재시동 방법을 알아내는 것이다. 두 가지 성공 호출을 모두 처리하는 것이다.appDataService.getData()예외는 물론

둘 중 하나 또는 다른 하나(지연과 시간을 어디에 두느냐에 따라 다름)에 대해 작업을 할 수 있지만 두 가지 모두에 대해서는 작동하지 않는다.

업데이트 4

나는 여기에 최신 코드를 가지고 있어.

그대로 실행하면 getData가 성공하는데 놀랍게도 일시 중지 또는 중지 동작이 중지되고 다시 시작하도록 허용한다.스톱 액션이 재시동을 허용한다는 것이 놀랍다.takeWhile(() => !this.isPollingCancelled),효과를 취소할 수 있을 겁니다

또한, 만약true에게 전해지다getData이것은 오류를 관찰할 수 있는 원인이 될 것이다.여론조사는 계속되지만(원하는 대로, 즉 오류 발생 시에도 재시도) 일단 우리가 일시 중지 조치를 발송할 때, 그것은 투표를 멈추지 않고, 우리가 정지 조치를 발송할 때, 그것은 멈추지만, 그러면 다시 시작되지 않는다.나는 이길 수 없다.

업데이트 5

계속적인 투표 효과가 취소되기 때문에 아래처럼 매번 다시 만들 수 있을 거라고 생각했다.

    import { Injectable, OnInit, OnDestroy } from '@angular/core';
    import { createEffect, Actions, ofType } from '@ngrx/effects';
    import { select, Store } from '@ngrx/store';
    import { mergeMap, map, catchError, takeWhile, delay, tap, switchMap } from 'rxjs/operators';
    import { AppState } from './app.state';
    import { Observable, of } from 'rxjs';
    import { AppDataService } from '../app-data.service';
    import * as appActions from './app.actions';

    @Injectable()
    export class AppEffects {
      private isPollingCancelled: boolean;
      private isPollingPaused: boolean;

      constructor(
        private actions$: Actions,
        private store: Store<AppState>,
        private appDataService: AppDataService
      ) { }

      public startPolling$ = createEffect(() => this.actions$.pipe(
        ofType(appActions.startPolling),
        tap(_ => console.log('app effect started polling')),
        tap(() => {
          this.isPollingCancelled = false;
          this.isPollingPaused = false;
          this.createPollingEffect(); // <--- recreate the effect every time
        }),        
          mergeMap(() =>
            this.appDataService.getData()
              .pipe(                        
                switchMap(data => {              
                  return [appActions.getDataSuccess(data)
                  ];
                  }),
                catchError(err => of(appActions.getDataFail(err)))
              ))
        ));

      public pausePolling$ = createEffect(() => this.actions$.pipe(
        ofType(appActions.pausePolling),
        tap(_ => this.isPollingPaused = true),
        tap(_ => console.log('app effect pause polling')),
      ), { dispatch: false });
      
      public cancelPolling$ = createEffect(() => this.actions$.pipe(
        ofType(appActions.cancelPolling),
        tap(_ => {
          this.isPollingCancelled = true;
          this.isPollingPaused = true;
        }),
        tap(_ => console.log('app effect cancel polling')),
      ), { dispatch: false });

      public continuePolling$: any;

      private createPollingEffect(): void {
        console.log('creating continuePolling$');
        this.continuePolling$ = createEffect(() => this.actions$.pipe(
          ofType(appActions.getDataSuccess, appActions.getDataFail),
          tap(data => console.log('app effect continue polling')),
          delay(3000),
          takeWhile(() => !this.isPollingCancelled),
          mergeMap(() =>
            this.appDataService.getData(false)
              .pipe(
                tap(data => console.log('app effect continue polling - inner loop')),

                switchMap(data => {
                  return [appActions.getDataSuccess(data)
                  ];
                }),
                catchError(err => of(appActions.getDataFail(err)))
              ))
        ), { resubscribeOnError: true });
      } 
    }

그래서 지금.startPolling나는 부른다this.createPollingEffect()계속적인 투표 효과를 창출하기 위해서.

하지만 이런 시도를 해보니 투표는 절대 시작되지 않는다.

업데이트 6

내게 효과가 있는 것 같은 해결책을 생각해 냈다.

나는 다음과 같은 것을 가지고 있다.

public startPolling$ = createEffect(() => this.actions$.pipe(
        ofType(dataActions.startPollingGetData),
        tap(_ => this.logger.info('effect start polling')),
        tap(() => this.isPollingActive = true),
        switchMap(_ => this.syncData())
      ), { dispatch: false });
      
    public continuePolling$ = createEffect(() => this.actions$.pipe(
        ofType(dataPlannerActions.DataSuccess,
          dataActions.DataFail),
        tap(_ => this.logger.debug('data effect continue polling')),
        tap(_ => this.isInDelay = true),
        delay(8000),
        tap(_ => this.isInDelay = false),
        switchMap(_ => this.syncData())
      ), { dispatch: false });


    public stopPolling$ = createEffect(() => this.actions$.pipe(
        ofType(dataActions.stopPollingData),
        tap(_ => this.isPollingActive = false),
        tap(_ => this.logger.info('data effect stop polling')),
        map(_ => dataActions.stopPollingDataSuccess())
      ), { dispatch: false });


    private syncData(): Observable<Action> {
        const result$: Observable<Action> = Observable.create(async subscriber => {
          try {
            // If polling "switched off", we just need to return anything (not actually used)
            // Id isInDelay, we may be restating while we still have a pending delay.
            // In this case we will exit, and just wait for the delay to restart
            // (otherwise we can end up with more than one call to this)
            if (this.isInDelay || !this.isPollingActive) {
              subscriber.next("");
              return;
            }

나는 여기서 몇 개의 "플랙스"를 사용한다. 나는 당신이 이것을 하는 더 "rxy"적인 방법이 될 것이라고 확신한다.

사실, 이 게시물은 어떻게 하면 가능한지 알아보세요.isInDelay(위의 생산 코드에 이 코드를 입력하기 위해 이동하면 됨)

대신 다음을 사용하십시오.

public startPolling$ = createEffect(() => this.actions$.pipe(
  ofType(appActions.startPolling),    
  tap(_ => console.log('app effect started polling')),  
  tap(() => this.isPollingActive = true),        
  switchMap(() =>
    this.appDataSurvice.getData()
      .pipe(                        
        exhaustMap(data => {              
          return [appActions.getDataSuccess(data)];
        }),
        catchError(err => of(appActions.getDataFail(err)))
      ))
));

네가 그 문제에 접근한 방법은 칭찬할 만하다.나는 투표를 재개하는 것과 똑같은 문제에 직면했고 이 기사가 나를 도와주었다.

지금 내가 직면하고 있는 한 가지 문제는 3초 이내에 여론조사가 다시 시작되면(지정된 타이머) 서비스로 여러 통화가 걸려온다는 것이다.즉, 간격이 경과한 후에야 투표가 완전히 일시 중지/중지된다.따라서 타이머가 경과하기 전에 다시 시작하려고 하면 여러 개의 스레드가 실행된다.서비스 호출 @ https://angular-ngrx-polling3-j7b8st.stackblitz.io에 타임스탬프가 추가됨

서비스 요청은 각 여론 조사에서 두 번 발생한다.

내 질문/토론의 일환으로 이것을 가지고 있었지만, 조금 더 가시적으로 보이게 하기 위한 해결책으로 생각했지...

내게 효과가 있는 것 같은 해결책을 생각해 냈다.

나는 다음과 같은 것을 가지고 있다.

public startPolling$ = createEffect(() => this.actions$.pipe(
        ofType(dataActions.startPollingGetData),
        tap(_ => this.logger.info('effect start polling')),
        tap(() => this.isPollingActive = true),
        switchMap(_ => this.syncData())
      ), { dispatch: false });

    public continuePolling$ = createEffect(() => this.actions$.pipe(
        ofType(dataPlannerActions.DataSuccess,
          dataActions.DataFail),
        tap(_ => this.logger.debug('data effect continue polling')),
        tap(_ => this.isInDelay = true),
        delay(8000),
        tap(_ => this.isInDelay = false),
        switchMap(_ => this.syncData())
      ), { dispatch: false });


    public stopPolling$ = createEffect(() => this.actions$.pipe(
        ofType(dataActions.stopPollingData),
        tap(_ => this.isPollingActive = false),
        tap(_ => this.logger.info('data effect stop polling')),
        map(_ => dataActions.stopPollingDataSuccess())
      ), { dispatch: false });


    private syncData(): Observable<Action> {
        const result$: Observable<Action> = Observable.create(async subscriber => {
          try {
            // If polling "switched off", we just need to return anything (not actually used)
            // Id isInDelay, we may be restating while we still have a pending delay.
            // In this case we will exit, and just wait for the delay to restart
            // (otherwise we can end up with more than one call to this)
            if (this.isInDelay || !this.isPollingActive) {
              subscriber.next("");
              return;
            }

나는 여기서 몇 개의 "플랙스"를 사용한다. 나는 당신이 이것을 하는 더 "rxy"적인 방법이 될 것이라고 확신한다.

사실, 이 게시물은 어떻게 하면 가능한지 알아보세요.isInDelay(위의 생산 코드에 이 코드를 입력하기 위해 이동하면 됨)

@peterc와 @Ingo Bürk로부터의 입력을 바탕으로 모든 시나리오를 양성으로 테스트할 수 있었다.아래는 내 코드가 어떻게 보이는지 입니다.

@Effect()
      getPageData$ = this.actions$.pipe(
        ofType(actions.StartLoading),
        tap(() => {
          this.appService.isPollingActive = true;
        }),
        mergeMap(() =>
          this.appService.getData().pipe(
            switchMap((response: GridDataResponse) => {
              return [new actions.DoneLoading(response.data)];
            }),
            retry(1),
            catchError(err => {
              return of(new actions.FailedLoading());
            })
          ))
      );

      @Effect()
      public stopPolling$ = this.actions$.pipe(
        ofType(actions.StopPolling),
        tap(_ => {
          this.appService.isPollingActive = false;
        }),
        mergeMap(() => {
          return [new actions.ResetLoading()];
        })
      );

      @Effect()
      public continuePolling$ = this.actions$.pipe(
        ofType(actions.DoneLoading,
          actions.FailedLoading),
        switchMap(_ =>
          timer(this.appService.pollingTimer).pipe(
            takeUntil(this.actions$.pipe(ofType(actions.StopPolling))),
            mergeMap(() =>
            this.appService.getData().pipe(
              takeWhile(() => this.appService.isPollingActive),
              switchMap((response: GridDataResponse) => {
                return [new actions.DoneLoading(response.data)];
              }),
              catchError(err => {
                return of(new actions.FailedLoading());
              })
            ))
          )
      )
      );

참조URL: https://stackoverflow.com/questions/58426291/angular-ngrx-effect-to-continue-polling-a-service-only-called-the-first-time

반응형