如何在多级嵌套函数中等待异步http订阅完成
How to wait for asynchronous http subscription to complete in multilevel nested function
我是 Rxjs 的新手。我正在尝试为我的 nativescript-angular 应用程序实施 canActivate 保护。基本上,如果用户在存储中有 JWT 令牌,它将尝试从服务器检查 JWT 有效性,如果令牌仍然有效则登陆主页,如果无效则重定向到登录页面。以下代码在服务器启动时工作正常,但是,当服务器关闭时,它将登陆一个不完整的主页,因为无法从服务器获取数据。发生这种情况是因为同步 if(this.authService.isLoggedIn())
不等待异步 http 调用和 return true(下面的代码)。我希望它在服务器关闭时通过阻塞重定向到登录页面而不是主页,我尝试了各种方法(甚至重构代码)但其中 none 有效。 and 与我的情况非常相似,但解决方案没有帮助。这是我的代码:
编辑:
auth.service.ts(更新)
import { getString, setString, remove } from 'application-settings';
interface JWTResponse {
status: string,
success: string,
user: any
};
export class AuthService {
tokenKey: string = 'JWT';
isAuthenticated: Boolean = false;
username: Subject<string> = new Subject<string>();
authToken: string = undefined;
constructor(private http: HttpClient) { }
loadUserCredentials() { //call from header component
if(getString(this.tokenKey)){ //throw error for undefined
var credentials = JSON.parse(getString(this.tokenKey));
if (credentials && credentials.username != undefined) {
this.useCredentials(credentials);
if (this.authToken)
this.checkJWTtoken();
}
}
}
checkJWTtoken() {
this.http.get<JWTResponse>(baseURL + 'users/checkJWTtoken') //call server asynchronously, bypassed by synchronous code in canActivate function
.subscribe(res => {
this.sendUsername(res.user.username);
},
err => {
this.destroyUserCredentials();
})
}
useCredentials(credentials: any) {
this.isAuthenticated = true;
this.authToken = credentials.token;
this.sendUsername(credentials.username);
}
sendUsername(name: string) {
this.username.next(name);
}
isLoggedIn(): Boolean{
return this.isAuthenticated;
}
destroyUserCredentials() {
this.authToken = undefined;
this.clearUsername();
this.isAuthenticated = false;
// remove("username");
remove(this.tokenKey);
}
clearUsername() {
this.username.next(undefined);
}
auth-guard.service.ts
canActivate(route: ActivatedRouteSnapshot,
state:RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean{
this.authService.loadUserCredentials(); //skip to next line while waiting for server response
if(this.authService.isLoggedIn()){ //return true due useCredentials called in the loadUserCredentials
return true;
}
else{
this.routerExtensions.navigate(["/login"], { clearHistory: true })
return false;
}
}
app.routing.ts
const routes: Routes = [
{ path: "", redirectTo: "/home", pathMatch: "full" },
{ path: "login", component: LoginComponent },
{ path: "home", component: HomeComponent, canActivate: [AuthGuard] },
{ path: "test", component: TestComponent },
// { path: "item/:id", component: ItemDetailComponent },
];
编辑2:
我试过这些代码:
auth-guard.service.ts
canActivate(route: ActivatedRouteSnapshot,
state:RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean{
this.authService.loadUserCredentials();
if(this.authService.isLoggedIn()){
console.log("if");
return true;
}
else{
console.log("else");
this.routerExtensions.navigate(["/login"], { clearHistory: true })
return false;
}
}
auth.service.ts
async checkJWTtoken() {
console.log("checkJWTtoken1")
try{
console.log("try1")
let res = await this.http.get<JWTResponse>(baseURL + 'users/checkJWTtoken').toPromise();
console.log("try2")
this.sendUsername(res.user.username);
console.log("try3")
}
catch(e){
console.log(e);
this.destroyUserCredentials();
}
console.log("checkJWTtoken2")
}
// checkJWTtoken() {
// this.http.get<JWTResponse>(baseURL + 'users/checkJWTtoken')
// .subscribe(res => {
// // console.log("JWT Token Valid: ", JSON.stringify(res));
// this.sendUsername(res.user.username);
// },
// err => {
// console.log("JWT Token invalid: ", err);
// this.destroyUserCredentials();
// })
// }
这是控制台输出:
JS: Angular is running in the development mode. Call enableProdMode() to enable the production mode.
JS: checkJWTtoken1
JS: try1
JS: if
JS: ANGULAR BOOTSTRAP DONE. 6078
JS: try2
JS: try3
JS: checkJWTtoken2
编辑 3:
tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"noEmitHelpers": true,
"noEmitOnError": true,
"lib": [
"es6",
"dom",
"es2015.iterable"
],
"baseUrl": ".",
"paths": {
"*": [
"./node_modules/tns-core-modules/*",
"./node_modules/*"
]
}
},
"exclude": [
"node_modules",
"platforms"
]
}
请提前告知并感谢。
你需要有一个Observable来判断用户是否登录,有很多方法可以做到,其中之一就是这个
在authservice中定义一个行为主体
userLoggedIn = new BehaviorSubject(null)
并发出用户登录事件
loadUserCredentials() { //call from header component
if(getString(this.tokenKey)){ //throw error for undefined
var credentials = JSON.parse(getString(this.tokenKey));
if (credentials && credentials.username != undefined) {
this.useCredentials(credentials);
this.userLoggedIn.next(true)
//Make sure you set it false when the user is not logged in
if (this.authToken)
this.checkJWTtoken();
}
}
}
并修改 canActivate 方法以等待观察者
canActivate(route: ActivatedRouteSnapshot,
state:RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean{
return Observable.create(observer => {
this.authService.userLoggedIn.subscribe(data => {
if(data !== null) {
if(data) observer.next(true)
else observer.next(false)
}
}
}
我建议将您的代码重构为 return 从 loadUserCredentials 方法可观察的,它应该仅在完成 JWT 令牌检查时触发该可观察的。
此外,将您的 canActivate 守卫重构为 return Observable 而不是 static/primitive 值。
您可以修改您的服务,使 checkJWTToken 在返回之前等待 http 调用完成
import 'rxjs/add/operator/toPromise';
//...
async checkJWTtoken() {
try
{
let res = await this.http.get<JWTResponse>(baseURL + 'users/checkJWTtoken').toPromise(); //call server synchronously
this.sendUsername(res.user.username);
}
catch(e)
{
console.log(e);
this.destroyUserCredentials();
}
}
Edit 当使用 async/await 时,代码只是 'waits' 你在 checkJWTToken
方法中使用 await 的地方。但是来自父执行的代码继续正常
您需要使用 async/await 调用链,直到您真正想要等待的地方(在 canActivate 中)
后卫
async canActivate(...) {
//..
await this.authService.loadUserCredentials();
服务
async loadUserCredentials(){
//...
await this.checkJWTtoken();
async checkJWTtoken() {
//...
//let res = await this.http.get<JWTResponse>(baseUR
我创建了一个 stackblitz demo
我是 Rxjs 的新手。我正在尝试为我的 nativescript-angular 应用程序实施 canActivate 保护。基本上,如果用户在存储中有 JWT 令牌,它将尝试从服务器检查 JWT 有效性,如果令牌仍然有效则登陆主页,如果无效则重定向到登录页面。以下代码在服务器启动时工作正常,但是,当服务器关闭时,它将登陆一个不完整的主页,因为无法从服务器获取数据。发生这种情况是因为同步 if(this.authService.isLoggedIn())
不等待异步 http 调用和 return true(下面的代码)。我希望它在服务器关闭时通过阻塞重定向到登录页面而不是主页,我尝试了各种方法(甚至重构代码)但其中 none 有效。
编辑: auth.service.ts(更新)
import { getString, setString, remove } from 'application-settings';
interface JWTResponse {
status: string,
success: string,
user: any
};
export class AuthService {
tokenKey: string = 'JWT';
isAuthenticated: Boolean = false;
username: Subject<string> = new Subject<string>();
authToken: string = undefined;
constructor(private http: HttpClient) { }
loadUserCredentials() { //call from header component
if(getString(this.tokenKey)){ //throw error for undefined
var credentials = JSON.parse(getString(this.tokenKey));
if (credentials && credentials.username != undefined) {
this.useCredentials(credentials);
if (this.authToken)
this.checkJWTtoken();
}
}
}
checkJWTtoken() {
this.http.get<JWTResponse>(baseURL + 'users/checkJWTtoken') //call server asynchronously, bypassed by synchronous code in canActivate function
.subscribe(res => {
this.sendUsername(res.user.username);
},
err => {
this.destroyUserCredentials();
})
}
useCredentials(credentials: any) {
this.isAuthenticated = true;
this.authToken = credentials.token;
this.sendUsername(credentials.username);
}
sendUsername(name: string) {
this.username.next(name);
}
isLoggedIn(): Boolean{
return this.isAuthenticated;
}
destroyUserCredentials() {
this.authToken = undefined;
this.clearUsername();
this.isAuthenticated = false;
// remove("username");
remove(this.tokenKey);
}
clearUsername() {
this.username.next(undefined);
}
auth-guard.service.ts
canActivate(route: ActivatedRouteSnapshot,
state:RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean{
this.authService.loadUserCredentials(); //skip to next line while waiting for server response
if(this.authService.isLoggedIn()){ //return true due useCredentials called in the loadUserCredentials
return true;
}
else{
this.routerExtensions.navigate(["/login"], { clearHistory: true })
return false;
}
}
app.routing.ts
const routes: Routes = [
{ path: "", redirectTo: "/home", pathMatch: "full" },
{ path: "login", component: LoginComponent },
{ path: "home", component: HomeComponent, canActivate: [AuthGuard] },
{ path: "test", component: TestComponent },
// { path: "item/:id", component: ItemDetailComponent },
];
编辑2: 我试过这些代码:
auth-guard.service.ts
canActivate(route: ActivatedRouteSnapshot,
state:RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean{
this.authService.loadUserCredentials();
if(this.authService.isLoggedIn()){
console.log("if");
return true;
}
else{
console.log("else");
this.routerExtensions.navigate(["/login"], { clearHistory: true })
return false;
}
}
auth.service.ts
async checkJWTtoken() {
console.log("checkJWTtoken1")
try{
console.log("try1")
let res = await this.http.get<JWTResponse>(baseURL + 'users/checkJWTtoken').toPromise();
console.log("try2")
this.sendUsername(res.user.username);
console.log("try3")
}
catch(e){
console.log(e);
this.destroyUserCredentials();
}
console.log("checkJWTtoken2")
}
// checkJWTtoken() {
// this.http.get<JWTResponse>(baseURL + 'users/checkJWTtoken')
// .subscribe(res => {
// // console.log("JWT Token Valid: ", JSON.stringify(res));
// this.sendUsername(res.user.username);
// },
// err => {
// console.log("JWT Token invalid: ", err);
// this.destroyUserCredentials();
// })
// }
这是控制台输出:
JS: Angular is running in the development mode. Call enableProdMode() to enable the production mode.
JS: checkJWTtoken1
JS: try1
JS: if
JS: ANGULAR BOOTSTRAP DONE. 6078
JS: try2
JS: try3
JS: checkJWTtoken2
编辑 3:
tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"noEmitHelpers": true,
"noEmitOnError": true,
"lib": [
"es6",
"dom",
"es2015.iterable"
],
"baseUrl": ".",
"paths": {
"*": [
"./node_modules/tns-core-modules/*",
"./node_modules/*"
]
}
},
"exclude": [
"node_modules",
"platforms"
]
}
请提前告知并感谢。
你需要有一个Observable来判断用户是否登录,有很多方法可以做到,其中之一就是这个
在authservice中定义一个行为主体
userLoggedIn = new BehaviorSubject(null)
并发出用户登录事件
loadUserCredentials() { //call from header component
if(getString(this.tokenKey)){ //throw error for undefined
var credentials = JSON.parse(getString(this.tokenKey));
if (credentials && credentials.username != undefined) {
this.useCredentials(credentials);
this.userLoggedIn.next(true)
//Make sure you set it false when the user is not logged in
if (this.authToken)
this.checkJWTtoken();
}
}
}
并修改 canActivate 方法以等待观察者
canActivate(route: ActivatedRouteSnapshot,
state:RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean{
return Observable.create(observer => {
this.authService.userLoggedIn.subscribe(data => {
if(data !== null) {
if(data) observer.next(true)
else observer.next(false)
}
}
}
我建议将您的代码重构为 return 从 loadUserCredentials 方法可观察的,它应该仅在完成 JWT 令牌检查时触发该可观察的。
此外,将您的 canActivate 守卫重构为 return Observable 而不是 static/primitive 值。
您可以修改您的服务,使 checkJWTToken 在返回之前等待 http 调用完成
import 'rxjs/add/operator/toPromise';
//...
async checkJWTtoken() {
try
{
let res = await this.http.get<JWTResponse>(baseURL + 'users/checkJWTtoken').toPromise(); //call server synchronously
this.sendUsername(res.user.username);
}
catch(e)
{
console.log(e);
this.destroyUserCredentials();
}
}
Edit 当使用 async/await 时,代码只是 'waits' 你在 checkJWTToken
方法中使用 await 的地方。但是来自父执行的代码继续正常
您需要使用 async/await 调用链,直到您真正想要等待的地方(在 canActivate 中)
后卫
async canActivate(...) {
//..
await this.authService.loadUserCredentials();
服务
async loadUserCredentials(){
//...
await this.checkJWTtoken();
async checkJWTtoken() {
//...
//let res = await this.http.get<JWTResponse>(baseUR
我创建了一个 stackblitz demo