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).
中阅读有关 HttpClient
和 handleError
函数的更多信息
编辑您的评论:
使用 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())
);
我正在尝试将我的旧 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).
HttpClient
和 handleError
函数的更多信息
编辑您的评论:
使用 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())
);