多个 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.
)
}
我正在 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.
)
}