Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Return value with asynchronous functions in Typescript

I have the following TS function:

CheckRegUser(u: User): boolean {
    let b: boolean = true;
    let toSend = {
      email: u.email
    };
    this.http.post("http://localhost:8080/", toSend).subscribe((data: Valid) => {
      if(!data.validity){
        b = false;
      }
    });
    console.log(b);
    return b;
}

Here I am connecting to an ExpressJS backend and I am getting back a boolean result. This part is working fine, but the problem is that the "return b" statement is executed before that the value of b is changed in "this.http", so b is always true no matter what the response from Express is.

I know that TS and JS are asynchronous and this problem is caused because of that, but I couldn't find the correct method to solve my issue. Any help would be appreciated.

like image 363
RoyNasr Avatar asked Jul 04 '18 05:07

RoyNasr


People also ask

Can async function return a value?

Async methods can have the following return types: Task, for an async method that performs an operation but returns no value. Task<TResult>, for an async method that returns a value.

How do you handle asynchronous calls in TypeScript?

Asynchronous functions are prefixed with the async keyword; await suspends the execution until an asynchronous function return promise is fulfilled and unwraps the value from the Promise returned.

Do async functions return immediately?

With that design, you call the asynchronous function, passing in your callback function. The function returns immediately and calls your callback when the operation is finished. With a promise-based API, the asynchronous function starts the operation and returns a Promise object.


2 Answers

You can try with async await

  async CheckRegUser(u: User): Promise<boolean> {

    let toSend = {
      email: u.email
    };
    let k = await this.http.post("http://localhost:8080/", toSend).subscribe((data: Valid){
      let b: boolean = true;
      if(!data.validity){
        b = false;
      }
      return b
    });
    console.log(k);
    return k;
}
like image 198
brk Avatar answered Oct 19 '22 05:10

brk


I had this common problem when I was just starting to learn Observables/Reactive programming with a Google Maps app as well :D!

Spot on, you realised that asynchronous function hasn't mutated the b boolean flag yet when the function synchronously returns b (that defaulted to true);

To fix this it might be easier to restructure your function to return the Observable to the caller.

and maybe look up Observable chaining or similar concept Promise chaining.

Old example:

CheckRegUser(u: User): boolean {
    const b: boolean = true;
    const toSend = {
      email: u.email
    };

    const httpPost: Observable<aJsonObject> = this.http.post("http://localhost:8080/", toSend)
    const httpPostSubscription: Subscription = httpPost
      .subscribe((data: Valid) => { // This is asynchronous
        if (!data.validity) {
          b = false;
        }
      });
    console.log(b);

    return b; // This is effectively sync returning your default value `b = true`;
  }

It would be better if you could rename your variables to be clearer. b or isDataValid. Might also be good practice to start functional-style with const variables to help avoid mutability issues.

If you're more familiar with Promises, you could also try to promisify the httpClient.post Observable.

General gist of it is to pass the Observable back to the caller and .subscribe it there. When something is returning asynchronously, you should return the asynchronicity all the way back to the top.

Refactor code to reflect these practices

  CheckRegUser(user: User, httpClient: HttpClient): Observable<Valid> {
    // 1. Data to send
    type  EmailPost                  = { email: string }
    const emailJsonToSend: EmailPost = { // I prefer explicit typing wherever possible :tada: :D
      email: user.email
    };

    // 2. POST the data to the web server
    const emailHttpPostObs: Observable<Valid> = httpClient.post("http://localhost:8080/", emailJsonToSend);

    return emailHttpPostObs;
  }

  CallerSubmitUserFunction(user: User, httpClient: HttpClient) {
    // Made some assumptions, please comment and we can work out a better solution
    // Explicitly typed things as an example.

    // 1. You have a user e.g. new User(name:'myUser', email: '[email protected]');
    const userToSend: User = user;
    // 2. You POST the user.email and get response.
    const validatedUserDataObs: Observable<Valid> = CheckRegUser(userToSend, httpClient);

    // 3. You check whether the server accepted the data and whether it was valid.
    const validatedUserDataObs: Subscription = validatedUserDataObs
      .subscribe((data: Valid) => {
        // Perform data validation or Error checking here.

        // If data is valid, 
        if (dataValidationFunction()) {
          // Do something here
          // Instead of trying to return the boolean in the asynchronouse function.
          // Directly do something after the server responds (whenever it happens).
        }
      }) // catch Error


    // It would also be better if you could rename your variables to be more self-indicative
    // var dataIsValid

  }
like image 35
mittens pair Avatar answered Oct 19 '22 04:10

mittens pair