0

I'm currently learning more about the asynchronous aspect of angular and typescript so forgive me if my knowledge is lacking, I am currently facing a problem where I'm trying to implement promises to make a function which contains a for loop finish first before calling the next function,

Here is a sample code where the goal is where I have a for loop to go through all forms and in the loop, is to call a service to get the necessary data to be placed into the object then proceed to create a new object with said data and pushing the newly created data into an array, which after this function ends and the next function will run to submit the array to a service.

For example I have a form with a size of 2, I would like the for loop to end before calling the last resolve() to return to submitObj() and finally calling sendTestObject(),

in the component.ts

 submitObj() {

    // call function to create object and wait for it to finish
    // via a promise.then()
    this.createTestObject().then((){

        //then finally call another service to give created object
        this.sendTestObject();
    });
    
}
 
 
 createTestObject() {
    const testObjList: testActvity[] = [];
    let count = 1;
    return new Promise ((resolve) => {

    // How do I make it so that this for loop goes in order
    // and finishes first
    // before going to the next line of code?
    for (const i in this.testFormArray.controls) {
      testObj = new Actvity();
      let promise = new Promise((resolve,reject) =>{
      
      this.testService.getTestData().then(res => {
        console.log(count);
        if (res) {
            if (res.results) {
                this.testArrayForm.controls[i].get('testForm').patchValue({
                    "Id": res.results.Id,
                    "type": res.results.type,
                })
            }
        }      
      })}
      
      resolve();
    }
  });
  
  promise.then(()=> {
     console.log("in then() of " + count);
     testObj.testForm = this.testArrayForm.controls[i].get('testForm').value;
     testObjList.push(testObj);
     count ++;
  })

  //How do I wait for the for loop to finish first before running this?
  console.log("FINISHED)"
  resolve();
}

in the service.ts

getTestData(): Promise<any>{
    let res = this.http.get("testData.json")
    .toPromise()
    .then( res => {
      return res ;
    }). catch (err => {
        return err ;
    });
     return new Promise ((resolve, reject) => {
       resolve(res);
     });
 }

2 Answers2

0

Here is my solution. I'm not using your code because I cannot test it but I am showing the general idea. Basically I get rid of the for loop and instead iterate on the elements (in your case the form inputs) make a recursive chain if promises. Once the final one resolves, we know that we've processed all the elements:

const data = ['foo', 'bar'];

function processElement(index: number): void {
    console.log('do something with element: ' + index + ' which is: ' + data[index]);
}

function processAllElements(index: number, count: number): Promise<void> {
    return new Promise((resolve, reject) => {
        if(index == count) {
            resolve();
            return;
        }
        
        processElement(index);
        
        return processAllElements(index + 1, count);
    });
}

function run(): void {
    processAllElements(0, data.length)
        .then(() => {
            console.log('All done');
        });
}

run();
Lyubomir Vasilev
  • 2,757
  • 14
  • 21
0

why not use "concat" rxjs operator (Angular works well with observables without need to use Promise)? A simple example:

If we has a function that return an observable:

obs(value){
   return of(value).pipe(delay(1000))
}

And concat an an array of observables

const $concatObs=concat(...[0,1,2,3].map(x=>this.obs(x)));

We can subscribe

$concatObs.subscribe(res=>{
      this.value=res;
})

A simple stackblitz

Update "cacth error". The problem with concat if that our function can throw an error. well we can use catchError (another rxjs operator) to take acount this.

If we "pipe" the observables to make that if there an error return an object {error:....}

const $concatObs = concat(
  ...[0, 1, 2, -2, 3, 4,"finished"].map(x => this.obs(x)
     .pipe(catchError(error=>{
          return of({error:error})
      })))
);

In subscribe we can check if error

$concatObs.subscribe(res => {
  if (res.error)
  {
    console.log(res.error)
  }else
    this.value = res;
});
Eliseo
  • 29,422
  • 4
  • 13
  • 37