Angular HttpClient возвращает сервис слишком рано - PullRequest
0 голосов
/ 08 января 2019

Я пытаюсь преобразовать мой старый код Ionic Angular Http в новый формат Httpclient, но служба Get слишком рано возвращает управление вызывающей функции, поэтому не возвращает возвращенные данные.

Я пытался использовать async / await, но это не имеет никакого значения для потока управления.

Я новичок в наблюдаемых, так что я уверен, что это что-то не так, но я не могу понять, что.

Это функции из моего кода с новым форматом функции getAsJson, использующие функцию подписки Httpclient. Я только хочу вернуть данные из http-вызова, поэтому я не включил «наблюдаем: ответ» в параметре параметров.

loadLoyaltyDetails вызывает getLoyaltyDetails, который выполняет несколько других операций, отличных от показанных здесь, а затем вызывает getAsJson.

Функции:

loadLoyaltyDetails(): Promise<void> {
  return this.loyalty.getLoyaltyDetails().then(data => {
    console.log("in loadLoyaltyDetails, data =", data);
  })
  .catch(this.handleServiceLoadingError.bind(this))
}

getLoyaltyDetails(): Promise<LoyaltyDetails> {
  return this.callApi.getAsJson("/loyalty/url");
}

getAsJson(path: string): Promise<any> {
  let opts = this.getRequestOptions();
  console.log("in getAsJson, path = ", path);

  return this.http.get<HttpResponse<HttpEventType.Response>>(`${environment.API_URL}${path}`, opts)
    .subscribe(
      (res) => {
        console.log("in getAsJson, res = ", res);
        return res;
      },
      (err) => {
        console.log("in getAsJson, err = ", err);
        this.checkResponseForUnauthorized(err);
      }
    )
}

Сообщения журнала консоли

in getAsJson, path = /loyalty/url

in loadLoyaltyDetails, data = 
Object { closed: false, _parent: null, _parents: null, _subscriptions: (1) […], syncErrorValue: null, syncErrorThrown: false, syncErrorThrowable: false, isStopped: false, destination: {…} }

Using current token

in getAsJson, path = /other/url

in getAsJson, res = {…}
    endDate: "2019-01-08"
    numberOfShiftsRequired: 18
    numberOfShiftsWorked: 0
    startDate: "2019-01-08"

in getAsJson, res = Array []

Как показывают сообщения в журнале, loadLoyaltyDetails сначала вызывает getAsJson, но сразу же возвращает кучу разбойников. Затем getAsJson вызывается другой функцией, получает обратно данные для первого вызова, а затем для второго вызова.

Я ожидал, что строка 'in loadLoyaltyDetails, data =' появится после возвращения первого набора данных.

Это то, что я не могу понять, т. Е. Как я могу гарантировать, что контроль не будет возвращен loadLoyaltyDetails до того, как данные будут возвращены?

1 Ответ

0 голосов
/ 08 января 2019

Функция subscribe немедленно возвращает объект Subscribetion и не приостанавливает выполнение вашего кода до тех пор, пока подписанная наблюдаемая реально не выдаст значение. Subscribtion объекты не используются для получения данных из Observable, но только для отмены подписки на Observable, на который вы ранее подписались (обратите внимание, что вам не нужно отписываться от Observables, возвращаемых HttpClient, когда они завершаются и, следовательно, отписываются автоматически).

Вызывая return this.http.get(..).subscribe(..), вы возвращаете этот (вам бесполезный) объект подписки вплоть до вашей функции loadLoyaltyDetails(), где вы регистрируете его как объект data.

Вместо этого вы должны возвращать Observables вплоть до того момента, когда вам действительно понадобятся данные из Observable (я полагаю, это loadLoyaltyDetails() для вас). Здесь вы подписываетесь, и в функции subscribe вы делаете то, что вам нужно делать с объектами, испускаемыми вами Observable (в вашем случае тело ответа http). Обычно вы устанавливаете для некоторой переменной компонента, отображаемой в вашем HTML-шаблоне, значение, генерируемое Observable. Вы можете даже отложить подписку на свой шаблон с помощью AsyncPipe и вообще не подписываться вручную.

Если вам не нужно обрабатывать полный HttpResponse, а хотите получить только тело JSON и обрабатывать ошибки, вы можете сделать что-то вроде:

localLoyaltyDetails: LoyaltyDetails;

// Note that this function returns nothing especially no Subscribtion object
loadLoyaltyDetails(): void {
  // supposing this is where you need your LoyaltyDetails you subscribe here
  this.loyalty.getLoyaltyDetails().subscribe(loyaltyDetails => {
    // handle your loyaltyDetails here
    console.log("in loadLoyaltyDetails, data =", loyaltyDetails);
    this.localLoyaltyDetails = loyaltyDetails;
  });
}

getLoyaltyDetails(): Observable<LoyaltyDetails> {
  // We call getAsJson and specify the type we want to return, in this case 
  // LoyaltyDetails. The http response body we get from the server at the given url, 
  // in this case "/loyalty/url", has to match the specified type (LoyaltyDetails).
  return this.callApi.getAsJson<LoyaltyDetails>("/loyalty/url");
}

// Don't subscribe in this function and instead return Observables up until the 
// point where you actually need the data from the Observable.
// T is the type variable of the JSON object that the http get request should return.
getAsJson<T>(path: string): Observable<T> {
  let opts = this.getRequestOptions(); 
  console.log("in getAsJson, path = ", path);

  return this.http.get<T>(`${environment.API_URL}${path}`, opts)
    .pipe(
      // you could peek into the data stream here for logging purposes 
      // but don't confuse this with subscribing
      tap(response => console.log("in getAsJson, res = ", response)),
      // handle http errors here as this is your service that uses the HttpClient
      catchError(this.handleError) 
    );
}

// copied from the Angular documentation
private handleError(error: HttpErrorResponse) {
  if (error.error instanceof ErrorEvent) {
    // A client-side or network error occurred. Handle it accordingly.
    console.error('An error occurred:', error.error.message);
  } else {
    // The backend returned an unsuccessful response code.
    // The response body may contain clues as to what went wrong,
    console.error(
      `Backend returned code ${error.status}, ` +
      `body was: ${error.error}`);
  }
  // return an observable with a user-facing error message
  return throwError(
    'Something bad happened; please try again later.');
};

Подробнее о функциях HttpClient и handleError можно прочитать в Angular HttpClient Docs . Вы также можете написать функцию handleError, которая возвращает значение по умолчанию для ошибок, например, в Angular Tutorial (Http Error Handling) .


Изменить относительно вашего комментария:

Создание Observable из вашего Обещания с помощью функции defer (генерация Observable и, следовательно, выполнение Обещания откладывается до тех пор, пока подписчик фактически не подпишется на Observable).

import { defer } from 'rxjs';

// defer takes a Promise factory function and only calls it when a subscriber subscribes 
// to the Observable. We then use mergeMap to map the authToken we get from  
// getLoggedInToken to the Observable we get from getAsJson.
getLoyaltyDetails(): Observable<LoyaltyDetails> {
  return defer(this.login.getLoggedInToken)
    .pipe(
      mergeMap(authToken =>
        this.callApi.getAsJson<LoyaltyDetails>(authToken, "/loyalty/details/NMC")
      )
    );
}

Обратите внимание, что loadLoyaltyDetails ничего не возвращает, т. Е. void.

private details: LoyaltyDetails;

loadLoyaltyDetails(): void {
  // supposing this is where you need your LoyaltyDetails you subscribe here
  this.loyalty.getLoyaltyDetails().subscribe(loyaltyDetails => {
    console.log("in loadLoyaltyDetails, data =", loyaltyDetails);

    // set a local variable to the received LoyaltyDetails
    this.details = loyaltyDetails;
  });
}

Поскольку ваш loadLoyaltyDetails ничего не возвращает, вы просто вызываете функцию в тот момент, когда вам нужно, чтобы она была выполнена.

this.loader.wraps<void>(
  this.loadShifts().then(() => this.loadLoyaltyDetails())
);
...