在rxjs中,如何通过从不同API接收的数据数组链接映射?

时间:2019-01-27 17:49:37

标签: javascript rxjs

我正在调用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))”的结果进行映射是最好的方法吗?或者您还有其他建议吗?

3 个答案:

答案 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操作之前完成。

任何评论都会受到赞赏,希望这对以后的工作有所帮助。