NgRX Effect для загрузки медиафайлов и отслеживания прогресса; как бороться с потоком и дросселированием - PullRequest
0 голосов
/ 28 августа 2018

Я немного борюсь с пониманием и применением @Effects для своего варианта загрузки эпизода. Мне помогли в другом вопросе, и это моя последняя смесь.

Быстрый обзор: пользователь нажимает кнопку загрузки, которая отправляет действие DOWNLOAD_EPISODE, записанное в первом эффекте. Вызов загрузки возвращает поток HttpEvents и окончательный HttpResponse.

Во время event.type === 3 я хочу сообщить о прогрессе загрузки. Когда event.type === 4 тело прибыло, и я могу назвать успех, который, например, может создать Blob.

Сервис episodesService:

download( episode: Episode ): Observable<HttpEvent<any>> | Observable<HttpResponse<any>> {

  // const url = encodeURIComponent( episode.url.url );
  const url = 'https%3A%2F%2Fwww.sample-videos.com%2Faudio%2Fmp3%2Fcrowd-cheering.mp3';
  const req = new HttpRequest( 'GET', 'http://localhost:3000/episodes/' + url, {
    reportProgress: true,
    responseType: 'blob'
  } );
  return this.http.request( req );
}

downloadSuccess( response: any ): Observable<any> {
  console.log( 'calling download success', response );
  if ( response.body ) {
    var blob = new Blob( [ response.body ], { type: response.body.type } );
    console.log( 'blob', blob );
  }
  return of( { status: 'done' } );
}

getHttpProgress( event: HttpEvent<any> | HttpResponse<Blob> ): Observable<DownloadProgress> {

  switch ( event.type ) {
    case HttpEventType.DownloadProgress:
      const progress = Math.round( 100 * event.loaded / event.total );
      return of( { ...event, progress } );

    case HttpEventType.Response:
      const { body, type } = event;
      return of( { body, type, progress: 100 } );

    default:
      return of( { ...event, progress: 0 } );
  }
}

Эффекты:

@Effect()
downloadEpisode$ = this.actions$.pipe(
  ofType<episodeActions.DownloadEpisodes>( episodeActions.DOWNLOAD_EPISODE ),
  switchMap( ( { payload } ) => this.episodesService.download( payload )
    .pipe(
      switchMap( (response: HttpEvent<any> | HttpResponse<any>) => this.episodesService.getHttpProgress( response ) ), //merge in the progress
      map( ( response: fromServices.DownloadProgress ) => {

        // update the progress in the episode
        //
        if ( response.type <= 3 ) {
          return new episodeActions.DownloadProgressEpisodes( { ...payload, download: {
            progress: response.progress
          } }  );

        // pass the Blob on the download response
        //
        } else if ( response.type === 4 ){
          return new episodeActions.DownloadEpisodesSuccess( response );
        }
      } ),
      catchError( error => of( new episodeActions.DownloadEpisodesFail( error ) ) ),
    )
  )
)

@Effect( { dispatch: false } )
processDownloadEpisodeSuccess$ = this.actions$.pipe(
  ofType<any>( episodeActions.DOWNLOAD_EPISODE_SUCCESS ),
  switchMap( ( { payload } ) => this.episodesService
    .downloadSuccess( payload ).pipe(
      tap( response => console.log( 'response', payload,response ) ),

      //  catchError(err => of(new episodeActions.ProcessEpisodesFail(error))),
    )
  )
)

Я очень доволен этим решением, однако мне не нравится предложение If ELSE в MAP как часть эффекта DOWNLOAD_EPISODE. ​​

В идеале я хочу разделить поток там, если тип равен 3, я хочу пойти по маршруту А, который всегда отправляет episodeActions.DownloadProgressEpisodes с полезной нагрузкой Episode. Всякий раз, когда это тип 4, последнее отправленное событие, я хочу получить маршрут B в потоке и вызвать episodeActions.DownloadEpisodesSuccess с телом ответа.

Я пытался добавить больше эффектов, но всегда оказывался в ситуации, когда поток результатов this.episodesService.download будет либо обновлением прогресса, либо телом ответа. Для обновления хода выполнения я требую, чтобы запись Эпизода могла вызывать редуктор и обновлять ход Эпизода.

Я пытался использовать вещи в качестве установки filter( response => response.type === 4 ) после DownloadProgressEpisodes, но до DownloadEpisodesSuccess, надеясь, что это позволит итерации до того, как фильтр справится с прогрессом, а затем отфильтрует все остальное до Успешного действия. Затем я узнал, что потоки не работают.

Я также пытался last(), который работал, но не позволял мне отправлять действия отклика (журнал консоли работал), но только действие DownloadEpisodesSuccess после отправки last().

Итак, если есть возможность разделить поток и работать с event.type 3 по-другому, тогда event.type 4 меня это заинтересует.

***** UPDATE *******

Я хочу оставить исходный вопрос, однако я столкнулся с требованием регулирования, потому что я отправлял много действий для больших файлов. Я пробовал оператор throttle, но это подавляло бы выбросы, но могло также подавлять последний выброс с телом ответа. Я попытался разделить его на partition, но я не думаю, что это возможно. После момента лампочки я решил создать 2 эффекта для DOWNLOAD_EPISODE, один из которых захватывает только все event.type === 3 и регулирует его, а другой эффект использует оператор last и работает только с event.type === 4.

См. Код:

  @Effect( )
  downloadEpisode$ = this.actions$.pipe(
    ofType<episodeActions.DownloadEpisodes>( episodeActions.DOWNLOAD_EPISODE ),
    switchMap( ( { payload } ) => this.episodesService.download( payload )
      .pipe(
        switchMap( (response: HttpEvent<any> | HttpResponse<any>) => this.episodesService.getHttpProgress( response ) ),
        throttleTime( 500 ),
        map( ( response: fromServices.DownloadProgress ) => {
          console.log('Type 3', response);
          // update the progress in the episode
          if ( response.type <= 3) {
            return new episodeActions.DownloadProgressEpisodes( { ...payload, download: {
              progress: response.progress
            } }  );
          }
        } ),
        catchError( error => of( new episodeActions.DownloadEpisodesFail( error ) ) ),
      )
    )
  )

  @Effect( )
  downloadEpisodeLast$ = this.actions$.pipe(
    ofType<episodeActions.DownloadEpisodes>( episodeActions.DOWNLOAD_EPISODE ),
    switchMap( ( { payload } ) => this.episodesService.download( payload )
      .pipe(
        switchMap( (response: HttpEvent<any> | HttpResponse<any>) => this.episodesService.getHttpProgress( response ) ),
        last(),
        map( ( response: fromServices.DownloadProgress ) => {
          console.log('Type 4', response);
          if ( response.type === 4 ){
            return new episodeActions.DownloadEpisodesSuccess( response, { ...payload, download: {
              progress: response.progress
            } } );
          }
        } ),

        catchError( error => of( new episodeActions.DownloadEpisodesFail( error ) ) ),
      )
    )
  )

Как вы думаете, это лучшее решение? Тип 3 и 4 разделены, и я могу задушить его.

* Update2: Это создает проблему, при которой Progress Action с прогрессом 97% может быть вызвано после действия Download Success с прогрессом 100%. Каждый раз, когда я сталкиваюсь с новым вызовом ...

SampleTime(500), кажется, работает, так как он не генерирует событие типа 3 после того, как событие типа 4 вступило в силу.

* update3: Я только что узнал, что я звоню Download дважды в результате, вздох. Я вернулся на круги своя, пытаясь задушить поток HttpEvent из episodeService.download.

1 Ответ

0 голосов
/ 29 августа 2018

Я думаю, что если вы не хотите иметь оператор if else, вам придется создавать различные эффекты.

В текущем вы отправите новое действие, например, DownloadEpisodeProgess, с типом в полезной нагрузке. Затем вы создадите два эффекта, которые слушают это действие, и отфильтруют их соответственно по типу: один эффект будет использоваться для отправки DownloadProgressEpisodes, другой - для DownloadEpisodesSuccess.

Другим возможным решением был бы оператор , но я не использовал его в сочетании с эффектами NgRx.

Также следует иметь в виду, что для каждой подписки вы платите за производительность, лично я не против утверждения if else.

...