如何在多级嵌套函数中等待异步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