Angular HttpClient获取服务返回得太早

时间:2019-01-08 12:59:08

标签: angular ionic3 rxjs angular-httpclient

我正在尝试将旧的Ionic Angular Http代码转换为新的Httpclient格式,但是Get服务将控制权返回给调用函数为时过早,因此它无法获取返回的数据。

我尝试使用async / await,但这对控制流没有影响。

我是可观察对象的新手,所以我确定这是我做得不好的事情,但我不知道要做什么。

这些是我代码中的函数,使用Httpclient的订阅功能,具有getAsJson函数的新格式。我只想从http调用中返回数据,所以我没有在options参数中包括“ observe:'response'”。

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,但立即得到一堆gobbledegook。然后,另一个函数将调用getAsJson,先回传第一个调用的数据,然后再返回第二个调用的数据。

我期望在返回第一组数据后出现“在loadLoyaltyDetails,data =”行。

这是我不知道的,即如何确保在返回数据之前不将控件返回到loadLoyaltyDetails?

1 个答案:

答案 0 :(得分:3)

subscribe函数立即返回一个Subscribtion对象,直到订阅的observable实际发出一个值之前,它不会暂停执行代码。 Subscribtion对象不是用来从Observable获取数据的,而只是用来取消订阅先前订阅的Observable的(请注意,您不必取消订阅HttpClient返回的Observable的内容)完成并因此自动退订)。

通过调用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.');
};

您可以在Angular HttpClient Docs中详细了解HttpClienthandleError函数。您还可以编写一个handleError函数,该函数针对诸如Angular Tutorial (Http Error Handling)中的错误之类的错误返回默认值。


编辑您的评论

使用defer函数从您的Promise中生成一个Observable(将Observable的生成以及Promise的执行推迟到订阅者实际订阅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())
);