如何在多级嵌套函数中等待异步http订阅完成

时间:2018-04-03 13:31:15

标签: angular rxjs angular2-nativescript angular-route-guards

我是Rxjs的新手。我正在尝试为我的nativescript-angular应用程序实现一个canActivate防护。基本上,如果用户在存储中具有JWT令牌,它将尝试从服务器检查JWT有效性,并且如果令牌仍然有效则登陆主页,或者如果其无效则重定向到登录页面。以下代码在服务器启动时工作正常,但是,当服务器关闭时,它将落在不完整的主页上,因为无法从服务器获取数据。发生这种情况是因为同步if(this.authService.isLoggedIn())不等待异步http调用并返回true(下面的代码)。我希望它通过阻止服务器关闭时重定向到登录页面而不是主页,我尝试了各种方法(甚至重构代码),但没有一个工作。 Thisthis与我的情况非常相似,但解决方案没有帮助。这是我的代码:

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

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

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

EDIT3:

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"
    ]
}

请提前通知并表示感谢。

3 个答案:

答案 0 :(得分:0)

您需要有一个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();
      }  
    }
  }

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

答案 1 :(得分:0)

我建议重构你的代码以从loadUserCredentials方法返回Observable,并且只有在完成JWT标记检查时它才会触发observable。

另外,重构你的canActivate后卫以返回Observable而不是static / primitive值。

答案 2 :(得分:0)

您可以修改服务以使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时,代码只是等待&#39;你在checkJWTToken方法中使用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

相关问题