我正在用angular 5重写一个应用程序,该应用程序从服务器获取数据并下载一些pdf文件。该服务器是具有3个api的soap接口:
1-获取广告系列列表
2-按广告系列ID获取文件数组
3-按文件ID获取文件
我已经使用纯XMLHTTPRequest和一些递归函数来完成此操作,这些递归函数填充了“广告系列”对象,提取了封面并一个接一个地保存了所有文件。
我现在想要得到的是避免回调地狱,并使用httpclient和angular中的observables获得相同的结果,但是在研究了rxjs库两天后,我仍然不知道该怎么做。
我已经通过httpclient成功地获取了数据,并且我对可观察的操作者做了很多尝试。 事件的示意流程为:
获取广告系列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”运算符从数组中创建新的可观察对象吗?还是阵列上的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函数,现在它返回一个可观察的对象。 现在,这可行...我想知道是否有更简单的方法,是否缺少某些东西。 我也在尝试使用catchError运算符处理错误,但是如果我使用throw,则可观察对象的类型将变为空,并且我必须放置类型“ any”而不是“ Campaign []”以使其起作用。 最后一件事,我希望这个可观察的对象每次使用新文档进行更新时都返回campaigns对象,以便在下载文件时显示封面,而不是在最后显示全部……可以这样做吗?
答案 0 :(得分: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.
)
}