Reputation: 803
Is there a way to make forEach loop in Typescript wait so that asynchronous code like http call can complete properly.
Let's say I have three Arrays a[], b[] & c[] in an Angular Component. There are three functions, the latter two depends on the completion of previous functions.
loadA(){
this.http.get<a[]>(http://getA).subscribe(a=> this.a = a,
()=>loadB());
}
loadB(){
this.a.forEach((res, index)=>{
this.http.get<b[]>('http://getBbyA/res').subscribe(b=> this.b.push(...b),
()=> {
if(index===this.a.length-1){
loadC());
}
}
});
loadC(){
this.b.forEach(res=>{
this.http.get<c[]>('http://getCbyB/res').subscribe(c=> this.c.push(...c));
});
}
Now for the second Method the forEach loop makes it unpredictable to call the loadC() function after the b[] array is properly loaded with data fetched from http call. How to make forEach loop in loadB() wait for all the http results are fetched and then call loadC() to avoid unpredictability?
Update (With RxJs Operators):
I have tried the following in my project:
loadData(): void {
this.http.post<Requirement[]>(`${this.authService.serverURI}/requirement/get/requirementsByDeal`, this.dealService.deal).pipe(
concatAll(), // flattens the array from the http response into Observables
concatMap(requirement => this.http.post<ProductSet[]>(`${this.authService.serverURI}/productSet/getProductSetsByRequirement`, requirement).pipe( // loads B for each value emitted by source observable. Source observable emits all elements from LoadA-result in this case
concatAll(), // flattens the array from the http response of loadB
concatMap(pSet => this.http.post<Product[]>(`${this.authService.serverURI}/product/get/productsByProductSet`, pSet).pipe( // foreach element of LoadB Response load c
map(product => ({requirement, pSet, product})) // return a object of type { a: LoadAResult, b: LoadBResult, c: LoadCResult}
))
)),
toArray()
).subscribe((results: { requirement: Requirement, productSet: ProductSet, product: Product }[] => {
results.forEach(result => {
this.requirements.push(...result.requirement);
this.productSets.push(...result.productSet);
this.products.push(...result.product);
});
}));
}
But I am still getting some error (TS2345). Where I am going wrong?
Upvotes: 1
Views: 868
Reputation: 1698
There are several ways to achieve this. I suggest to use rxJs Operators concatAll and concatMap so fire the httpCalls in sequence. I prepared a quick snippet. It first loads A, then for each element in A it loads B and for each element in B it loads C. You may need to adjust the aggregation of results depending on your needs
load(): void {
this.http.get<LoadAResult[]>("/loadA").pipe(
concatAll(), // flattens the array from the http response into Observables
concatMap(a => this.http.get<LoadBResult[]>("/loadB").pipe( // loads B for each value emitted by source observable. Source observable emits all elements from LoadA-result in this case
concatAll(), // flattens the array from the http response of loadB
concatMap(b => this.http.get<LoadCResult[]>("/loadC").pipe( // foreach element of LoadB Response load c
map(c => ({a, b,c })) // return a object of type { a: LoadAResult, b: LoadBResult, c: LoadCResult}
))
)),
toArray()
).subscribe((results: { a: LoadAResult, b: LoadBResult, c: LoadCResult[]}[]) => {
// do something with results
}));
}
Upvotes: 1