NgRX Effect,用于下载媒体文件和调度进度;如何处理流和节流

时间:2018-08-28 11:23:58

标签: angular6 ngrx ngrx-effects

我在理解和应用@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))),
    )
  )
)

我对此解决方案感到非常满意,但是我不喜欢MAP中的If ELSE子句作为DOWNLOAD_EPISODE效果的一部分。

理想情况下,我想在那儿拆分流,如果类型为3,我想走路线A,该路线始终以Episode有效载荷调度episodeActions.DownloadProgressEpisodes。 每当它是类型4(最后一个发出的事件)时,我都希望在流中获得B路由,并使用响应主体调用episodeActions.DownloadEpisodesSuccess

我试图添加更多效果,但是我总是遇到这样的情况,this.episodesService.download的结果流要么是进度更新,要么是响应正文。对于进度更新,我要求Episode记录能够调用reducer并更新Episode的进度。

我尝试使用在filter( response => response.type === 4 )之后但在DownloadProgressEpisodes之前设置DownloadEpisodesSuccess的情况,希望它允许在过滤器处理进度之前进行迭代,然后过滤其余部分成功行动。 然后,我了解到这不是流的工作原理。

我还尝试了last()确实有效,但是它不允许我分派响应操作(控制台日志确实有效),但是仅分派last()之后的DownloadEpisodesSuccess操作。 / p>

因此,如果可以拆分流并以与event.type 3不同的方式处理event.type 4,则对此我会感兴趣。

*****更新*******

我想保留最初的问题,但是我确实遇到了节流的要求,因为我为大型文件调度了很多操作。 我尝试了throttle运算符,但它可以抑制发射,但也可以抑制响应主体的最后一个发射。我尝试使用partition进行拆分,但我认为这是不可能的。经过一个灯泡瞬间,我决定为DOWNLOAD_EPISODE创建2个效果,一个仅捕获所有event.type === 3并对其进行调节,而另一个效果则使用last运算符并且仅处理{{1 }}。

查看代码:

event.type === 4

您认为这是最好的解决方案吗?类型3和4被拆分了,我可以限制它。

* update2: 确实会产生一个问题,其中进度为100%的 @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 ) ) ), ) ) ) 操作之后可以触发进度为97%的Progress Action。 每当我遇到新的挑战...

Download Success似乎可以正常工作,因为Type 4事件进入后似乎并没有引发Type 3事件。

* update3:实际上,我发现我现在两次致电SampleTime(500),叹了口气。我回到第一个方格,试图限制来自EpisodeService.download的HttpEvent流。

1 个答案:

答案 0 :(得分:1)

我认为,如果您不想使用if else语句,则必须创建不同的效果。

在当前操作中,您将分派新操作,例如DownloadEpisodeProgess,其类型在有效载荷中。 然后,您将创建两个监听此动作的效果,并通过类型对它们进行相应的过滤,一个效果将用于调度DownloadProgressEpisodes,另一个效果用于调度DownloadEpisodesSuccess

另一种可能的解决方案是partition operator,但我没有将其与NgRx效果结合使用。

请记住,对于您进行的每个订阅都会有性能成本,我个人并不在乎if else语句。