I always encounter nested concatMap, the latter http requests base on the former results. See below example, I need get formConfig and then use the formConfig id to get application record, then xxx. Is it more readable and maintainable turning nested rxjs concatMap to aysnc await (Avoid callback hell)? Pros and cons? which one is easier to process error handling?
General nested concatMap
ngOnInit(){
this.http.get(`/app-config/${this.appType}`)
.pipe(
concatMap(formConfig => {
this.formConfig = formConfig;
return this.http.get(`/appl/${this.formConfig.id}`);
}),
concatMap(applRec => {
this.applRec = applRec;
return this.http.get(`/xxx/${this.applRec.recNo}`)
})
tap((val) => {this.xxx = val})
....
).subscribe();
}
Turn to async await toPromise
constructor(){
this.init().then();
}
async init() {
this.formConfig = await this.http.get(`/app-config/${this.appType}`).toPromise();
this.applRec = await this.http.get(`/appl/${this.formConfig.id}`).toPromise();
this.xxx = await this.http.get(`/xxx/${this.applRec.recNo}`).toPromise();
}
toPromise() is deprecated and is being removed, so you should probably avoid it. More info on that here: https://indepth.dev/posts/1287/rxjs-heads-up-topromise-is-being-deprecated#why-is-this-happening-to-me.
If you want to use async / await / promises you can use firstValueFrom(). The cons of using it are outlined in the docs here: https://rxjs.dev/api/index/function/firstValueFrom
WARNING: Only use this with observables you know will emit at least one value, OR complete. If the source observable does not emit one value or complete, you will end up with a promise that is hung up, and potentially all of the state of an async function hanging out in memory. To avoid this situation, look into adding something like timeout, take, takeWhile, or takeUntil amongst others.
So the disadvantage of promises is that you need to add a timeout or you risk a memory leak. However, I don't think you have to worry about this when using the observables from the HttpClient service, I've never seen one not emit a value or complete.
Since you're only looking for single values and not observable streams, I don't see any downside to using promises here. You can also just label ngOnInit as async.
async ngOnInit() {
this.formConfig = await firstValueFrom(this.http.get(`/app-config/${this.appType}`));
this.applRec = await firstValueFrom(this.http.get(`/appl/${this.formConfig.id}`));
this.xxx = await firstValueFrom(this.http.get(`/xxx/${this.applRec.recNo}`));
}