Angular下使用Observables及Promise实现Google CSE分页拉取异常问题
问题核心原理
所有问题的本质是JavaScript的异步执行机制:不管是Observable.subscribe的回调还是Promise.then的回调,都属于异步微任务,会等待当前主线程的所有同步代码执行完成后才会触发。
- 第一个Observable版本的问题:主线程执行到
subscribe后不会等待回调返回,直接向下执行console.log('StartIndex is: ', this.next.startIndex),此时this.next还未被异步回调赋值,是undefined,直接抛出未捕获的类型错误,后续的日志和while循环完全没有执行机会,所以只能拿到第一页数据。 - 第二个Promise版本的问题:调用
this.cse.getData()时没有加await,主线程不会等待then回调执行,第一次循环判断while(next)时,next还是初始值false,循环直接结束,等异步回调把next改成true的时候,循环已经终止,不会发起下一页请求。
最优实现方案
方案1:RxJS原生实现(Angular技术栈推荐)
用RxJS内置的expand操作符专门处理递归拉取下一页的场景,不需要把Observable转成Promise,更贴合Angular的技术设计:
import { expand, EMPTY, reduce } from 'rxjs'; // 组件内方法 getData(): void { if (!this.searchString) return; window.localStorage.setItem('searchString', this.searchString); this.results = []; // 发起首次请求,递归拉取全量数据 this.cse.getData(this.searchString, appKey, 1) .pipe( // 自动判断是否有下一页,有就继续发起请求 expand((data) => { const nextPage = data.queries.nextPage?.[0]; return nextPage ? this.cse.getData(nextPage.searchTerms, nextPage.cx, nextPage.startIndex) : EMPTY; }), // 合并所有页的返回结果 reduce((allResults, currentPage) => allResults.concat(currentPage.items), []) ) .subscribe({ next: (allItems) => { this.results = allItems; console.log('全量数据拉取完成', this.results); }, error: (err) => console.error('拉取失败', err) }) }
// 服务层调整,直接返回Observable即可 export class CseService { // 省略其他属性 constructor(private http: HttpClient ) { } getData(q: string, cx: string, start: number) { if (!q) return EMPTY; let url = this.buildUrl() + '&cx=' + cx; if (start > 10) { url += '&start=' + start; } url += '&q=' + q; return this.http.get(url).pipe(delay(1000)); } private buildUrl(): string { return baseUrl + '?key=' + key; } }
方案2:async/await实现(逻辑更直观)
如果更习惯同步写法的异步逻辑,可以用标准的async/await写法,不需要手动处理回调时序:
async getData() { if (!this.searchString) return; this.saveResults(); let start = 1; let hasNextPage = true; this.results = []; while (hasNextPage) { const data = await this.cse.getData(this.searchString, appKey, start); this.results = this.results.concat(data.items); const nextPage = data.queries.nextPage?.[0]; hasNextPage = !!nextPage; if (hasNextPage) start = nextPage.startIndex; } console.log('全量数据拉取完成', this.results); }
内容的提问来源于stack exchange,提问作者Uwe
相关产品推荐
相关产品推荐

