Promise in Angular 9

Viewed 5467

I'm trying to write a synchronous function call in Angular using a Promise, but it still gets a different result than expected. I have 2 functions that use the service that download data from the API:

getData1(shortcut: string): void {
  this.apiServ.getPrice(shortcut).subscribe(
    (price: CurrentPrice) => {
      this.price = price;
      console.log(1);
    },
    error => this.error = error
  );
}

getData2(shortcut: string): void {
  this.apiServ.getPrice(shortcut).subscribe(
    (price: CurrentPrice) => {
      this.price = price;
      console.log(2);
    },
    error => this.error = error
  );
}

I'm trying to make the next function call after the response is completed. So:

data(shortcuts) {
  this.getData1(shortcuts[0]);
  this.getData2(shortcuts[1]);
}

ngOnInit() {
  const myPromise = new Promise((resolve) => {
    this.data(this.shortcuts);
    resolve(3)
  });
  myPromise.then((value) => {console.log(value)});
}

But my browser prints this call order:

3 
1 
2

What am I doing wrong?
I also tried to use callbacks but the effect is always the same.

3 Answers

If you want to use promises you can use async/await but instead you can use rxjs for doing this.

Observable flow

I create two functions to simulate your problem, one is called getDataAsObservable which represents your getData method, and another one called getDataAsPromise which represents your promise, then I use the concat that is an observable creation method from rxjs which sequentially emits all values from given Observable and then moves on to the next.

I pass first$, second$, and third$ asynchronous functions to concat and to subscribe in order to print the result out.

Promise flow

Otherwise, if you use async/await, you only need to wait for the response.

  import { Observable, concat } from 'rxjs';
  import { delay } from 'rxjs/operators';


  async ngOnInit() {
    // first execute promise flow and wait for the response
    await this.executePromiseFlow();
    // after promise flow we can execute observable flow
    this.executeObservableFlow(); 
  }

  executePromiseFlow = async () => {
    // I use toPromise method to convert observable to a promise
    const first = await this.getDataAsObservable(1).toPromise();
    console.info('[Promise] output', first);
    const second = await this.getDataAsObservable(2).toPromise();
    console.info('[Promise] output', second);
    const third = await this.getDataAsPromise(3);
    console.info('[Promise] output', third);
  }

  executeObservableFlow = () => {
    const second$ = this.getDataAsObservable(2);
    const first$ = this.getDataAsObservable(1);
    const third$ = this.getDataAsPromise(3);

    concat(first$, second$, third$)
      .subscribe((output) => console.info('[Observable] output', output))
  }

  getDataAsObservable = (value: number) => { 
    return new Observable((observer) => {
      observer.next(value);
      observer.complete();
    }).pipe(
      delay(value * 2000), // simulate HTTP request time
    );
  }

  getDataAsPromise = (value: number) => {  
    return Promise.resolve(value);
  }

You can read more about async/await here and rxjs here.

You can check a simple reproduction of this here on Stackblitz

You could use zip from rxjs:

import { Observable, zip } from 'rxjs'; import { tap } from 'rxjs/operators';

getData1(shortcut: string): Observable<any>  {
  return this.apiServ.getPrice(shortcut).pipe(
    tap(
      (price: CurrentPrice) => {
        this.price = price;
        console.log(1);
      }
    )
  );
}

getData2(shortcut: string): Observable<any>  {
  return this.apiServ.getPrice(shortcut).pipe(
    tap(
      (price: CurrentPrice) => {
        this.price = price;
        console.log(2);
      }
    )
  );
}


data(shortcuts) : Observable<any> {
  const res1 = this.getData1(shortcuts[0]);
  const res2 = this.getData2(shortcuts[1]);
  return zip(res1, res2);
}

ngOnInit() {
  this.data(this.shortcuts).subscribe(() => {
    // your code
  })
}

Because they are promises you need to respect their resolve time, it means to wait until they are completed and that means you can't call them as a normal function when you rely on their result.

Also because it's an angular app try to convert them to rxjs stream via from function.

getData1(shortcut: string): Observable<CurrentPrice> { // <- return stream
  return this.apiServ.getPrice(shortcut).pipe(
    tap(console.log, console.log), // logs emits and errors
  );
}

getData2(shortcut: string): Observable<CurrentPrice> { // <- return stream
  return this.apiServ.getPrice(shortcut).pipe(
    tap(console.log, console.log), // logs emits and errors
  );
}

data(shortcuts): Observable<[CurrentPrice, CurrentPrice]> { // <- return stream
  return combineLatest([
    this.getData1(shortcuts[0]),
    this.getData2(shortcuts[1]),
  ]);
}

ngOnInit() {
  const myPromise = new Promise((resolve, error) => {
    // wait until completed
    this.data(this.shortcuts).pipe(
      take(1), // <- in promise we need just 1 emit
    ).subscribe(prices => resolve(prices), error); 
  });
  myPromise.then((value) => {console.log(value)}); // profit
}
Related