【问题标题】:multiple httpclient request that depends on data from another httpclient request using observables多个 httpclient 请求,依赖于使用 observables 的另一个 httpclient 请求的数据
【发布时间】:2019-12-25 20:28:36
【问题描述】:

我正在用 Angular 5 重写一个从服务器获取数据并下载一些 pdf 文件的应用程序。服务端是一个soap接口,有3个api:

1 - 获取广告系列列表

2 - 按活动 ID 获取文件数组

3 - 通过文件 id 获取文件

我已经用纯 XMLHTTPRequest 和一些递归函数完成了这项工作,这些函数填充了“campaigns”对象、提取封面并一个接一个地保存所有文件。

我现在想要的是避免回调地狱并使用httpclient和angular中可用的observables来获得相同的结果,但是经过两天对rxjs库的研究,我仍然不明白如何去做.

我已经使用 httpclient 成功获取了数据,并且我已经对 observable opertors 做了很多尝试。 事件流程示意图为:

获取广告系列 xml =>

将 xml 数据转换为一系列活动(使用地图运算符完成)=>

为每个活动获取 filesInfo =>

将 xml 数据转换为 filesInfo 数组 =>

为每个活动的每个文件获取该文件 =>

提取文件封面并保存文件。

我被困在这里:

getCampaigns():Observable<Campaign[]>{
    return this.getRawDataProvider.getRawData('campaignsList').pipe(
      map(rawCampaigns => {return this.createCampaignArray(rawCampaigns)})
    )
  }
this.getCampaigns().subscribe(result => {
  console.log(result) //correctly shows an array of campaigns (without the files of course)
})

现在呢?我应该使用“from”运算符从数组中创建新的 observable 吗?还是在阵列上进行一个 foreach 并为每个活动启动一个新的 http 客户端请求?最后,我需要一系列这样的活动:

[
  {
    name: 'campaign name',
    id: 1234,
    files:[
      {
        name:'file name',
        id: 4321,
        cover: 'base64string'
      }
    ]
  }
]

我需要文件一个接一个地下载,而不是并行下载

编辑: 还要感谢 dmcgrandle 的回答,我弄清楚了一些事情,经过几次尝试,我得出了这个结论:

updateCampaigns():Observable<Campaign[]>{
    let updatedCampaigns:Campaign[];
    return this.getRawDataProvider.getRawData( 'campaignList' ).pipe(
      map( rawCampaigns => {
        updatedCampaigns = this.createCampaignsArray( rawCampaigns );
        return updatedCampaigns;
      } ),
      switchMap( _campaigns => from( _campaigns ).pipe(
        concatMap( _campaign => this.getRawDataProvider.getRawData( 'campaignDocuments', _campaign.id ) )
      ) ),
      map((rawDocuments,i)=>{
        updatedCampaigns[ i ].documents = this.createDocumentsArray( rawDocuments );
        return;
      } ),
      reduce( () => {
        let { campaigns, documentsToDowloadIdList } = this.mergeWithStoredData( updatedCampaigns );
        updatedCampaigns = campaigns;
        return documentsToDowloadIdList;
      } ),
      switchMap( list => from( list ).pipe(
        concatMap( documentId => this.getRawDataProvider.getRawData( 'document', documentId ).pipe(
          map( b64file => { return { documentId:documentId, b64file:b64file } } )
        ) )
      ) ),
      concatMap( file => this.saveFile( file.b64file, file.fileId ) ),
      tap( fileInfo => {
        updatedCampaigns.forEach( campaign => { campaign.documents.forEach( document => {
          if( fileInfo.fileId == document.id ) {
            document.cover = fileInfo.b64cover;
            document.numPages = fileInfo.numPages;
          }
        } ) } )
      } ),
      reduce( () => {
        this.storageservice.set( 'campaigns' , updatedCampaigns );
        return updatedCampaigns;
      } )
    )
  }

请注意,我已经修改了 saveFile 函数,现在它返回一个 observable。 现在,这有效......我想知道是否有更简单的方法以及我是否遗漏了什么。 我也在尝试使用 catchError 运算符处理错误,但是如果我使用 throw,则 observable 的类型将变为 void,我必须将类型“any”而不是“Campaign []”以使其工作。 最后一件事,我希望这个可观察对象在每次使用新文档更新时返回活动对象,以便在下载文件时显示封面,而不是最后一起显示......可以这样做吗?

【问题讨论】:

  • 如果以下答案对您有所帮助,请考虑将其标记为正确和/或投票。谢谢。 :)

标签: nested observable angular-httpclient


【解决方案1】:

我建议您在一个函数中完成所有这些操作。一旦你有了一系列的活动,一次发射一个项目,并对每个项目进行转换,一次发射一个。然后最后将结果全部放入一个数组并从函数中返回。

在开发过程中,我还会在每个阶段插入 tap(),以查看每次转换后发生的情况,以确保其按预期运行。

使用伪代码,它看起来像这样:

getCovers(): Observable<Cover[]> {
  return this.getRawDataProvider.getRawData('campaignsList').pipe(
    map(rawCampaigns => this.createCampaignArray(rawCampaigns)),
    concatAll(), // emit the array one item at a time
    concatMap(campaign => this.getFileInfo(campaign)), // get FileInfo for each campaign
    concatMap(fileInfo => this.getTheFile(fileInfo)), // get the File for each fileInfo
    concatMap(file => this.saveTheFileAndGetCover(file)), // save the file, return the cover
    toArray() // put the results into an array for returning.
  )
}

【讨论】:

  • 在我看来,您似乎正试图将解决此问题的迭代方法塞进功能范式中,这将非常复杂且困难。我对您的数据了解得不够多,无法为您重新编写所有内容,但请尝试从数据流和转换该流的角度进行思考。例如,您有一个方法范围的数组updatedCampaigns,您在链中多次修改。不要那样做 - 而是让链本身携带活动数组中的项目,并在运行时对其进行修改。这可能允许您为每个文档发出一个新的活动......
  • 另一个提示 - 在您的 catchError() 中,返回可观察函数 throwError() 以将错误重新引入可观察流,不要使用 throw。文档here
  • 我试过了,但我发现我携带的对象的类型有问题....例如,我无法将 observable 分配给campaign.document 属性。如果我只使用“任何”类型,那么它会更容易,但是我失去了打字稿的用处,这似乎不是一个最佳实践。以第一个 switchmap 为例,如何将原始数组带入接下来的两个运算符?
  • 携带原始数组,只需将函数 this.getRawDataProvider.getRawData() 通过管道传输到 map() 运算符,该运算符将返回映射到包含您想要的两个数据的新数组 - 来自函数的返回和原始数组。请参阅示例 here,其中 Cartant 使用对象而不是数组,但同样的想法也适用。
猜你喜欢
  • 2011-03-13
  • 2018-10-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多