多个 httpclient 请求依赖于使用 observables 的另一个 httpclient 请求的数据

multiple httpclient request that depends on data from another httpclient request using observables

我正在 angular 5 中重写一个应用程序,它从服务器获取数据并下载一些 pdf 文件。服务器是带有 3 api:

的 soap 接口

1 - 获取活动列表

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

3 - 通过文件 id

获取文件

我已经使用纯 XMLHTTPRequest 和一些填充 "campaigns" 对象的递归函数完成了此操作,提取封面并一个接一个地保存所有文件。

我现在想得到的是避免回调地狱并使用httpclient和angular中可用的observables来获得相同的结果,但是经过两天对rxjs库的研究我仍然没有'我不知道该怎么做。

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

获取广告系列 xml =>

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

为每个活动获取文件信息 =>

将 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" 运算符从数组中创建新的可观察对象吗?或者数组上的 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 函数,现在它 return 是一个可观察对象。 现在,这有效......我想知道是否有更简单的方法以及我是否遗漏了什么。 我也在尝试使用 catchError 运算符处理错误,但是如果我使用 throw,则可观察对象的类型变为无效,我必须将类型 "any" 改为 "Campaign[]" 才能使其工作。 最后一件事,我更喜欢这个 observable 而不是 return 活动对象每次用新文档更新时,以便在下载文件时显示封面,而不是最后一起显示......这是可能的这样做?

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

在开发过程中,我还会在每个阶段插入 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.
  )
}