我正在尝试将旧的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?
答案 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中详细了解HttpClient
和handleError
函数。您还可以编写一个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())
);