繁体   English   中英

如何在多层嵌套函数中等待异步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调用并返回true(下面的代码)。 我希望它通过在服务器关闭时进行阻止来重定向到登录页面而不是首页,我尝试了各种方法(甚至重构代码),但是它们都不起作用。 非常类似于我的情况,但解决方案是没有帮助的。 这是我的代码:

编辑: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;
            }
    }

应用程序路由

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 },
];

Edit2:我尝试了以下代码:

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;
    }
}

验证服务

  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)
               }
               }
        }  

我建议重构您的代码以从loadUserCredentials方法返回Observable,并且仅当完成JWT令牌检查后才应触发该Observable。

另外,重构canActivate防护以返回Observable而不是静态/原始值。

您可以修改服务,以使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();
    }
  }  

编辑使用async / await时,代码只是在使用checkJWTToken方法中“等待”使用await的checkJWTToken 从父执行中执行代码继续正常进行

您需要在调用链上使用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演示

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM