Search code examples
javascriptangulartypescriptasync-awaithttpclient

Angular Asynchronous function with HttpClient


Ok, So I've been scratching my head over this all day. Essentially, I have a button that would withdraw a student from a class. That part is simple enough. HOWEVER, it is supposed to also check the database for a waiting list for that class, and enroll the next person in line if there is any.

So in my mind, the order of operations would be...

  1. store the course ID for future use
  2. withdraw Student A from the class (HTTP #1)
  3. get the waiting list for that class (this is where I use the saved ID)(HTTP #2)
  4. make an array of the response
  5. IF the array length is 0, finish
  6. IF the array length is greater than 0, continue
  7. create a student B object from array[0]
  8. enroll student B into the class (HTTP #3)
  9. delete student B from the waiting list. (HTTP #4)

I can write the code well enough to compile, and each individual part works fine. But together, my synchronous function becomes asynchronous and doesn't work as intended. The main culprit is step 3 and 4. While the step 3 is still pending, JS moves onto step 4 and the code stops because obviously the array is empty.

onWithdrawClass(row:any){
    let courseId = row.courseId;

    //display a confirmation message
    if(confirm("Are you sure you want to withdraw from this course?\n \nYou may not be able to get your seat back if you do.")){
      
      //use the api service to perform the withdraw and refresh the list
      this.api.deleteWithdraw(row.id).subscribe(res=>{
        alert("You have been successfully withdrawn from this course.");
          let ref = document.getElementById('refresh');
          ref?.click();
      },
      err=>{
        alert("Something went wrong with withdrawal.\nTry refreshing the page to see if it worked.");
      })

      //get waiting list for that course
      this.api.getWaitlistByCourseId(courseId).subscribe(res=>{
        this.courseById = res;
      })

      //do this if there is someone waiting for that class
      if(this.courseById.length > 0){
        //create student object
        this.studentObj.id = this.courseById[0].id;
        this.studentObj.courseId = this.courseById[0].courseId;
        this.studentObj.courseName = this.courseById[0].courseName;
        this.studentObj.courseInstructor = this.courseById[0].courseInstructor;
        this.studentObj.studentFirstName = this.courseById[0].studentFirstName;
        this.studentObj.studentLastName = this.courseById[0].studentLastName;
        this.studentObj.studentEmail = this.courseById[0].studentEmail;
        
        //enroll that student from wait list into course
        this.api.enrollStudent(this.studentObj).subscribe(res=>{
          console.log("waiting list student enrolled.")
        },
        err=>{
          console.log("error with enrolling waiting list student.")
        })
        //remove that student from wait list
        this.api.deleteFromWaitingList(this.courseById[0].id).subscribe(res=>{
          console.log("student removed from waiting list")
        },
        err=>{
          console.log("error with deleting student from waiting list")
        })
      }
    }
  }

and the api service ts

enrollStudent(data:any){
        return this.httpClient.post<any>("ENROLL-URL", data)
        .pipe(map((res:any)=>{
          return res;
        }))
      }

deleteWithdraw(id:number){
        return this.httpClient.put<any>("WITHDRAW-URL", id)
        .pipe(map((res:any)=>{
          return res;
        }))
      }

getWaitlistByCourseId(courseId:string){
        return this.httpClient.post<any>("WAITLIST-URL", courseId)
        .pipe(map((res:any)=>{
          return res;
        }))
      }

deleteFromWaitingList(id:number){
        return this.httpClient.put<any>("WAITLIST-DELETE-URL", id)
        .pipe(map((res:any)=>{
          return res;
        }))
      }

I figured using async/await would be the best way to do this "correctly", but I cannot figure out how to get a promise from an http call or await for it.

(ps. If you're looking at my httpClient calls and saying "You have .put for a delete and .post for a get" I know. It's weird. That was just the only way to get it to work with Cosmos DB. They all work, that's not the issue, I promise.)

SOLVED:

Thank you to munleashed for helping! His answer didn't exactly solve it in my case, but it was what got the gears turning in my head. For anyone looking here for solutions, here is what I ended with...

courseById: any[] = [];

async doTheThingZhuLi(courseId: undefined){
    this.response = await this.http.post<any>("WAITLIST-URL", courseId)
    .pipe(map((res:any)=>{
      this.courseById = res;
    })).toPromise();
  }

onClickWithdraw(row:any){
    if(confirm("Are you sure you want to withdraw from this course?")){
      this.doTheThingZhuLi(row.courseId).then(res=>{
        this.moveFromWaitList(); //this handles steps 6 - 9
      }
      ).finally(() =>{
        this.actuallyWithdrawTheStudent(row.id);
      });
    }
  }

Solution

  • Im not sure if i understood what you want to do exactly, but i see that you have a problem with synchronization of the api calls. If you want to wait for one API call to be finished in order to call another API call you have to use some of the RXJS pipeable operators. So for example:

    const api1 = this.service.getApi1();
    const api2 = this.service.getApi2();
    
    api1.pipe(switchMap(api1Result => {
    // Here you can use result of api1 call
    
    return ap2;
    }).subscribe(api2Result => {
    // Here you can use result of api2Call
    });
    

    Basically the mechanisam is similar how Promises works:

    console.log(1);
    const promise = new Promise((res) => {res()});
    promise.then(res => console.log('3'));
    console.log(4);
    
    // It will print 1 4 3 (3 at the end because its an asyns operation)
    

    The logic is the same with Observables and RXJS. Thats why you cannot sync your code with your approach. Look at my first example and start from that.