如何让回调转换的管道Observable在Angular中同步执行而非异步?
问题描述
在Angular组件中,原本通过路由解析器获取数据的Observable订阅逻辑如下:
ngOnInit(): void { console.log("start"); this._activatedRoute.data // 执行HTTP请求后返回Observable .subscribe((x) => { console.log("in subscribe") // 根据返回值设置组件变量 }); console.log("ngOnInit complete"); }
此时控制台输出符合预期:
start in subscribe ngOnInit complete
说明路由解析器的Observable是同步执行的,组件变量能在ngOnInit完成前设置好。
现在需要串联一个基于回调的谷歌地图API调用,尝试用concatMap实现串行执行,代码如下:
ngOnInit(): void { console.log("start"); this._activatedRoute.data // 执行HTTP请求后返回Observable .pipe(concatMap((data: { shopShowcase: ShopShowcase }) => this.executeGoogleAPIReturningCallbackAsObservable(data.shopShowcase)) ) .subscribe((x) => { console.log("in subscribe") // 根据返回值设置组件变量 }); console.log("ngOnInit complete"); } public executeGoogleAPIReturningCallbackAsObservable(shopShowcase: ShopShowcase): Observable<ShopShowcase> { console.log("in observable creation") // 此处定义svService和svLocationRequest return Observable.create((observer) => { svService.getPanorama(svLocationRequest, (data, status) => { console.log("in Callback"); let panoId = data.location.pano; shopShowcase.PanoId = panoId; observer.next(shopShowcase); observer.complete(); }) }); }
但实际控制台输出为:
start in observable creation ngOnInit complete in callback in subscribe
预期输出应是ngOnInit complete在in subscribe之后,需要让管道中的回调Observable同步执行,确保组件变量在ngOnInit完成前设置完毕。
原因分析
路由解析器的this._activatedRoute.data之所以同步,是因为路由解析器已经在组件初始化之前完成了HTTP请求并缓存了数据,所以订阅时会立即触发回调。而谷歌地图的getPanorama是异步API,即使封装成Observable,其内部的回调也是在JavaScript事件循环的异步队列中执行,concatMap只能保证Observable的订阅顺序是串行的,但无法改变异步操作的执行时机,所以ngOnInit会继续执行到结束,不会等待异步回调完成。
调整方案
要让异步操作在ngOnInit完成前执行完毕,可以将Observable转换为Promise,结合async/await语法实现同步等待:
- 修改
ngOnInit为异步方法,使用await等待Observable执行完成:
import { take } from 'rxjs'; async ngOnInit(): Promise<void> { console.log("start"); // 将Observable转换为Promise,take(1)确保只取第一个值并完成 const result = await this._activatedRoute.data .pipe( concatMap((data: { shopShowcase: ShopShowcase }) => this.executeGoogleAPIReturningCallbackAsObservable(data.shopShowcase) ), take(1) ) .toPromise(); console.log("in subscribe"); // 根据result设置组件变量 console.log("ngOnInit complete"); }
- (可选)优化Observable封装,使用
bindCallback替代手动创建Observable,代码更简洁:
import { bindCallback, map } from 'rxjs'; public executeGoogleAPIReturningCallbackAsObservable(shopShowcase: ShopShowcase): Observable<ShopShowcase> { console.log("in observable creation"); // 用bindCallback将基于回调的API转换为Observable const getPanorama$ = bindCallback(svService.getPanorama.bind(svService)); return getPanorama$(svLocationRequest).pipe( map(([data, status]) => { console.log("in Callback"); let panoId = data.location.pano; shopShowcase.PanoId = panoId; return shopShowcase; }) ); }
这样修改后,控制台输出会符合预期:
start in observable creation in Callback in subscribe ngOnInit complete
内容的提问来源于stack exchange,提问作者Shawn de Wet
相关产品推荐
相关产品推荐

