1

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?

1

1 Answer 1

1

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
}));

}

Sign up to request clarification or add additional context in comments.

9 Comments

Hi, I have tried your suggestion in my project (Please see UPDATE in the question), it is not working. I am getting some error (TS2345). Is there any syntactical error around "((results: { requirement: Requirement, productSet: ProductSet, product: Product }[] => {" ?
there was an ) missing. sry :) also note that if you just push the elements into your arrays, you'll have lot of duplicate entries in there.
I have added the ). Still I am getting the following error: ERROR in src/app/Services/requirement.service.ts(185,17): error TS2345: Argument of type '(results: { requirement: Requirement; productSet: ProductSet; product: Product; }[]) => void' is not assignable to parameter of type '(value: { requirement: Requirement; pSet: ProductSet; product: Product[]; }[]) => void'. Types of parameters 'results' and 'value' are incompatible.
Type '{ requirement: Requirement; pSet: ProductSet; product: Product[]; }[]' is not assignable to type '{ requirement: Requirement; productSet: ProductSet; product: Product; }[]'. Type '{ requirement: Requirement; pSet: ProductSet; product: Product[]; }' is not assignable to type '{ requirement: Requirement; productSet: ProductSet; product: Product; }'. Property 'productSet' is missing in type '{ requirement: Requirement; pSet: ProductSet; product: Product[]; }'.
rename your pSet parameter to productSet
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.