我正在调用API并接收结果数组,正在检查分页,如果存在更多页面,我将呼叫下一页,直到没有更多页面为止。
对于每个结果数组,我都调用另一个端点,并执行完全相同的操作:我收到一个结果数组,检查另一个页面并再次调用端点。洗涤,然后重复冲洗。
例如:
我想获取可能是分页响应的国家/地区列表,然后针对每个国家/地区获取要分页的城市列表。对于每个城市,我执行一组转换,然后存储在数据库中。
我已经尝试过了,但是被卡住了:
const grabCountries = Observable.create(async (observer) => {
const url = 'http://api.com/countries'
let cursor = url
do {
const results = fetch(cursor)
// results = {
// data: [ 'Canada', 'France', 'Spain' ],
// next: '47asd8f76358df8f4058898fd8fab'
// }
results.data.forEach(country => { observer.next(country) })
cursor = results.next ? `${url}/${results.next}` : undefined
} while(cursor)
})
const getCities = {
next: (country) => {
const url = 'http://api.com/cities'
let cursor = url
do {
const results = fetch(cursor)
// results = {
// data: [
// 'Montreal', 'Toronto',
// 'Paris', 'Marseilles',
// 'Barcelona', 'Madrid'
// ],
// next: '89ghjg98nd8g8sdfg98gs9h868hfoig'
// }
results.data.forEach(city => {
`**** What do I do here?? ****`
})
cursor = results.next ? `${url}/${results.next}` : undefined
} while(cursor)
}
}
我尝试了几种方法:
制作主题(有时我需要基于'grabCountries'的结果进行并行处理。例如,我可能想将城市与获取城市同时存储在数据库中。)
const intermediateSubject = new Subject()
intermediateSubject.subscribe(storeCountriesInDatabase)
intermediateSubject.subscribe(getCities)
我也尝试了管道和映射,但似乎基本上是同一回事。
在我撰写本文时,我想到了这种解决方案,并且似乎工作得很好,我只想知道我是否将其弄得太复杂了。在某些情况下,我需要连续执行多个API调用。 (想象一下,国家=>国家=>城市=>面包店=>评论=>评论=>答复)因此,在另一个观察者回调模式上进行这种怪异的映射可能会令人讨厌。
这就是我现在基本拥有的:
// grabCountries stays the same as above, but the rest is as follows:
const grabCities = (country) =>
Observable.create(async (observer) => {
const url = `http://api.com/${country}/cities`
let cursor = url
do {
const results = fetch(cursor)
// results = {
// data: [
// 'Montreal', 'Toronto',
// 'Paris', 'Marseilles',
// 'Barcelona', 'Madrid'
// ],
// next: '89ghjg98nd8g8sdfg98gs9h868hfoig'
// }
results.data.forEach(city => {
observer.next(city)
})
cursor = results.next ? `${url}/${results.next}` : undefined
} while (cursor)
})
const multiCaster = new Subject()
grabCountries.subscribe(multiCaster)
multiCaster.pipe(map((country) => {
grabCities(country).pipe(map(saveCityToDB)).subscribe()
})).subscribe()
multiCaster.pipe(map(saveCountryToDB)).subscribe()
tl; dr-我调用一个API,该API在数组中接收一组分页的结果,我需要遍历每个项目,并调用另一个api,该API接收另一个分页的结果集,每个组也在数组中。
是将一个可观察的嵌套在另一个内部,然后通过“ callApiForCountries.pipe(map(forEachCountryCallApiForCities))”的结果进行映射是最好的方法吗?或者您还有其他建议吗?
答案 0 :(得分:0)
您需要的是expand
operator。它具有递归行为,因此适合具有分页结果的想法。
答案 1 :(得分:0)
这是与下一个URL的顺序爬网一起工作的代码。 从{next:url}开始,直到res.next不可用。
of({next:http://api.com/cities}).pipe(
expand(res=>results.next ? `${url}/${results.next}` : undefined
takeWhile(res=>res.next!==undefined)
).subscribe()
答案 2 :(得分:0)
好的,所以我为此花了很多脑力,并提出了两个似乎可行的解决方案。
const nestedFlow = () => {
fetchAccountIDs.pipe(map(accountIds => {
getAccountPostIDs(accountIds) // Has the do loop for paging inside
.pipe(
map(fetchPostDetails),
map(mapToDBFormat),
map(storeInDB)
).subscribe()
})).subscribe()
}
const expandedflow = () => {
fetchAccountIDs.subscribe((accountId) => {
// accountId { accountId: '345367geg55sy'}
getAccountPostIDs(accountId).pipe(
expand((results) => {
/*
results : {
postIDs: [
131424234,
247345345,
],
cursor: '374fg8v0ggfgt94',
}
*/
const { postIDs, cursor } = results
if (cursor) return getAccountPostIDs({...accountId, cursor})
return { postIDs, cursor }
}),
takeWhile(hasCursor, true), // recurs until cursor is undefined
concatMap(data => data.postIDs),
map(data => ({ post_id: data })),
map(fetchPostDetails),
map(mapToDBFormat),
map(storeInDB)
).subscribe()
})
}
两者似乎都具有相似的性能。我读过一些文章,离开数据流是一个不好的做法,应该对所有内容进行管道传输,但是我不知道如何消除“ expandedFlow”中的第一个出口,因为“ expand”需要回调一个可观察到的东西,但是也许可以做到的。
现在,我只需要解决从getAccountPostIDs
中调用“ complete”到最后一条记录存储在DB中的竞赛条件问题。目前,在我的测试中,observer.complete
在3个upsert操作之前完成。
任何评论都会受到赞赏,希望这对以后的工作有所帮助。