Angular HttpClient 获取服务返回太早

Angular HttpClient Get Service Returning Too Early

我正在尝试将我的旧 Ionic Angular Http 代码转换为新的 Httpclient 格式,但是 Get 服务过早地 return 将控制权交给调用函数,所以它没有无法获得 returned 数据。

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

我是 observables 的新手,所以我确定这是我做的不正确,但我不知道是什么。

这些是我的代码中的函数,具有新格式的 getAsJson 函数,使用 Httpclient 的订阅功能。我只想 return 来自 http 调用的数据,所以我没有在选项参数中包含 "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,但会立即返回一堆乱码。然后 getAsJson 被另一个函数调用,接收第一次调用的数据,然后第二次调用。

我原以为 'in loadLoyaltyDetails, data = ' 行会在第一组数据 return 编辑后出现。

这是我无法弄清楚的,即如何确保在数据被 return 编辑之前,控件不会被 return 编辑到 loadLoyaltyDetails?

subscribe 函数 return 立即成为一个 Subscribtion 对象,并且不会暂停代码的执行,直到订阅的可观察对象实际发出一个值。 Subscribtion 对象不用于从 Observable 获取数据,而仅用于取消订阅您之前订阅的 Observable(请注意,您不必取消订阅由 HttpClient 因为他们完成并因此自动取消订阅)。

通过调用 return this.http.get(..).subscribe(..) 你 return 这个(对你没用的)订阅对象一直到你的 loadLoyaltyDetails() 函数,在那里你将它记录为 data 对象.

相反,你应该 return 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. You could also write a handleError function that returns a default value on errors like the one in the Angular Tutorial (Http Error Handling).

中阅读有关 HttpClienthandleError 函数的更多信息

编辑您的评论:

使用 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 return 什么都没有,即 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 return 什么都没有,您只需在需要执行时调用该函数即可。

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