Angular HttpClient获取服务返回太早



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

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

我对可观察性很陌生,所以我确信这是我做得不对的事情,但我不知道是什么。

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

loadLoyaltyDetails调用getLoyalty Details,它做了一些其他的事情,然后调用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?

subscribe函数立即返回Subscribetion对象,并且在订阅的可观察对象实际发出值之前不会暂停代码的执行。Subscribtion对象不用于从Observable获取数据,而仅用于取消订阅您之前订阅的Observable(请注意,您不必在HttpClient返回的Observables完成时取消订阅,因此自动取消订阅)。

通过调用return this.http.get(..).subscribe(..),您将这个(无用的)Subscription对象一直返回到loadLoyaltyDetails()函数,在那里您将其记录为data对象。

相反,您应该返回Observable,直到您实际需要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文档中阅读更多关于HttpClienthandleError函数的信息。您还可以编写一个handleError函数,该函数返回错误的默认值,如Angular Tutorial(Http错误处理)中的错误。


编辑您的评论:

使用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())
);

最新更新