0

I've found several articles here and on google discussing this but I just don't understand them. It seems that no matter where I place awaits and asyncs the code after my http.get call executes first. And the alert messages from the calling program after the point where I call my service doesn't show at all. I'm hoping someone can help me understand what I need to do in simple terms that a await/async newbie like me can understand. Thanks in advance for any help you can give.

The alerts that you see in the code appear on the page in this order:

  1. single-game.component.ts: "first time in the single game component.....")

  2. games.service.ts: "Do we get past the call/subscribe?"

  3. games.service.ts: "Service --> current game is..." and on this one the alert does show the JSON that's returned.

Message 1 should appear first then message 3 then 2. And the ones in single-game.component.getCurrentGame that come behind the call to the service should appear after that.

single-game.component.ts code:

  getCurrentGame() : void {
    const id = Number(this.route.snapshot.paramMap.get('id'));
    alert (`first time in the single game component and the value returned is ${this.currentGame?.Name}`)
    this.gamesService.getCurrentGame(id).subscribe(value => this.currentGame = value);

    // Neither of these two alerts ever show.
    alert (`back in the single game component and the value returned is ${this.currentGame?.Name}`)
    alert(`we're back but the above message didn't show`);    
  }

games-service.ts code

  getCurrentGame(gameId: number): Observable<Game> {
    game: GamesService;
    var url:string = this.gamesURLBase + `/GetSingleGameInfo/${gameId}`;
    var currentGame: Game |undefined;
    var strCurrentGame: string;

    this.http.get<Game[]>(url)
        .subscribe((value:Game[]) => {
          if (value.length > 0) {
            currentGame = value.find(element => element.GameId == gameId)
            alert(`Service --> currentGame is ${JSON.stringify(currentGame)}`)
          }; 
       })
    alert("Do we get past the call/subscribe?")
    return currentGame as unknown as Observable<Game>; 
  }

Thanks for taking the time to read this. And again, thanks in advance for any help you can give.

Denise
  • 61
  • 5
  • 1
    `async`/`await` only works with promises, not observables. Are you wanting to convert this code to use promises? – Nicholas Tower Jul 28 '23 at 20:12
  • Probably... tried that based on Joseep's answer and I'm closer but still not quite there. I'll add an "answer" detailing how far I got. – Denise Jul 29 '23 at 17:29

2 Answers2

0

In general, you seem to want to convert asynchronous code to behave like synchronous https://stackoverflow.com/a/748189/15439733 code, inevitably creating weird patches like the ones you're describing. Fortunately, though, Promises https://stackoverflow.com/a/37365955/15439733 are non-blocking, meaning your overall javascript code stack won't get stuck behind the promise, but the method will work as a synchronous code block.

async getCurrentGame(): Promise<void> {
    const id = Number(this.route.snapshot.paramMap.get('id'));
    alert(`first time in the single game component and the value returned is ${this.currentGame?.Name}`);

    this.currentGame = await this.getGame(id);

    alert(`back in the single game component and the value returned is ${this.currentGame?.Name}`);
    alert(`we're back but the above message didn't show`);    
}

getGame(id: number): Promise<Game> {
    return new Promise((resolve, reject) => {
        this.gamesService.getCurrentGame(id).subscribe(value => resolve(value), error => reject(error));
    });
}

Alternatively, look into the reactive approach. https://stackoverflow.com/a/37674668/15439733

currentGame$: Observable<Game>;

getCurrentGame(): void {
    const id = Number(this.route.snapshot.paramMap.get('id'));
    this.currentGame$ = this.gamesService.getCurrentGame(id);
}
<div *ngIf="currentGame$ | async as currentGame">
    <p>{{ currentGame.gameProperty }}</p>
</div>
Joosep Parts
  • 5,372
  • 2
  • 8
  • 33
  • Thanks for the help. But I read elsewhere that Observables are preferred over Promises. I've done a lot of fiddling with this over the weekend but don't seem to be getting anywhere. However, since I did make a tiny bit of progress I decided to post a new question with my new alerts and all. If you still feel like offering assistance (and THANKS! for your help - not your fault I'm just not getting it :) ) that question is at https://stackoverflow.com/questions/76804150/make-page-load-wait-for-subscribed-observable-to-finish – Denise Jul 31 '23 at 13:33
  • Hi there, @JoosepParts. Just wanted to pop back in and thank you for the help again. I was able to get things to work in an async manner (something I need to learn to do anyway). I didn't realize that the service was bringing back a single element array. Back in my component I modified the code to take the 0th element of the array and got the single game that I wanted. And it showed up on the HTML page properly. Yay! I'm going to post an answer to this question with the working code but just wanted to pop back in and say thanks for being so willing to jump in and help. – Denise Aug 01 '23 at 09:40
0

I finally figured out what I was doing wrong. I thought I was misunderstanding async operations but what really happened is I didn't notice the service wasn't pulling back a single game but rather a one element array containing a game. I guess that's the difference between using a real service and the tutorial example that was using a mocked-up thing. This is the tutorial I was using, in case anyone's interested --> https://angular.io/tutorial/tour-of-heroes/toh-pt4

Anyway, here are the full files that now work. I took out all the alerts but left a marker where I took the 0th element of the array that the service returns and assign that to currentGame. And now I can see the name and everything on my HTML page. Yay!!!

PS -- thanks very greatly to @JoosepParts for being so willing to jump in and try to help an obvious noob. :)

single-game.component.ts

import { Component, Input, OnInit } from '@angular/core';
import { Observable } from 'rxjs';

import { ActivatedRoute, ResolveEnd } from '@angular/router';
import { Game } from '../../interfaces/games';
import { Question } from 'src/interfaces/questions';
import { Answer } from 'src/interfaces/answers';
import { GamesService } from 'src/services/games.service';
import { JsonPipe } from '@angular/common';


@Component({
  selector: 'app-single-game',
  templateUrl: './single-game.component.html',
  styleUrls: ['./single-game.component.css']
})
export class SingleGameComponent {

@Input() game?: Game;
questions: Question[] = [];
answers: Answer[] = [];
currentGame!: Game;

constructor(
  private gamesService: GamesService,
  private route: ActivatedRoute,
) {}

   ngOnInit(): void {
    this.getCurrentGame();
  }

  getCurrentGame(): void {
    const id = Number(this.route.snapshot.paramMap.get('id'));
    this.gamesService.getCurrentGame(id)
      .subscribe(currentGame => {
        this.currentGame = currentGame[0]   // <-- this is what made the difference
    }); 
  }
}

games.service.ts

import { Injectable, OnInit, Pipe } from '@angular/core';
import { Observable, of, throwError } from 'rxjs';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import {catchError, map, tap} from 'rxjs/operators'


import { Game } from '../interfaces/games';
import { Question } from 'src/interfaces/questions';
import { Answer } from 'src/interfaces/answers';
import { ErrorHandlingService } from './error-handling.service';
import { JsonPipe } from '@angular/common';

@Injectable({
  providedIn: 'root'
})
export class GamesService {

  errHandler: ErrorHandlingService;
//   games: Game[] = [];
  

  constructor(private http: HttpClient) {
    this.errHandler = new ErrorHandlingService();
   }

  private gamesURLBase = 'http://localhost:5000/api/GamePlay';


  /* All Games */
  getGames(): Observable<Game[]> {
    var url:string = this.gamesURLBase + '/GetAllGames';

    //TODO:  Come back and see if the error handling really works
    return this.http.get<Game[]>(url)
      .pipe(
        catchError(this.errHandler.handleError<Game[]>('getGames', []))
      );
  }

  getCurrentGame(gameId: number): Observable<Game[]> {
  var url:string = this.gamesURLBase + `/GetSingleGameInfo/${gameId}`;
  // DEBUGGING NOTE: this is coming back as a single element array of games, not a single game
  return this.http.get<Game[]>(url)
    .pipe(
//        tap (data => alert(`in tap of http call and I can see ${JSON.stringify(data)} `)),
        catchError(this.errHandler.handleError<Game[]>('getCurrentGame'))
    );
  }
}
Denise
  • 61
  • 5