Ajax 在Aurelias HTTP客户端中捕获错误

Ajax 在Aurelias HTTP客户端中捕获错误,ajax,typescript,promise,aurelia,Ajax,Typescript,Promise,Aurelia,大家好(尤其是奥雷莉亚核心团队在这里闲逛) 我有一个aurelia应用程序,它使用“aurelia http客户端”向我的后端API发出请求 我的后端API是在Nancy上运行的基于C#的服务 在我的前端,Iv'e将http客户端抽象到我自己的网络库中,如下所示: import { inject } from 'aurelia-framework'; import { Router } from 'aurelia-router'; import { HttpClient } from 'aure

大家好(尤其是奥雷莉亚核心团队在这里闲逛)

我有一个aurelia应用程序,它使用“aurelia http客户端”向我的后端API发出请求

我的后端API是在Nancy上运行的基于C#的服务

在我的前端,Iv'e将http客户端抽象到我自己的网络库中,如下所示:

import { inject } from 'aurelia-framework';
import { Router } from 'aurelia-router';
import { HttpClient } from 'aurelia-http-client';
import environment from './environment';

@inject(HttpClient, Router)
export default class httpservice {

  private http: HttpClient = null;
  private router: Router = null;
  private authService: any = null;
  private authToken: string = "";

  constructor(HttpClient, Router) {

    this.http = HttpClient;
    this.router = Router;

    HttpClient.configure(http => {
      http.withBaseUrl(environment.servicebase);
    });

  }

  public setAuthService(authService: any) {
    this.authService = authService;
  }

  public get(url: string, authObject?: any): any {

    let myAuth = this.authService ? this.authService : authObject;
    let myToken = "";
    if (myAuth) {
      myToken = myAuth.getAuthToken();
    }

    let self = this;
    let client = this.http
      .createRequest(url)
      .asGet()
      .withHeader("AuthenticationToken", myToken)
      .withInterceptor({
        responseError(responseError) {
          console.log(responseError);
          if (responseError.statusCode === 401) {
            if (myAuth) {
              myAuth.destroySession();
            }
          }
          if (responseError.statusCode === 404) {
            self.router.navigateToRoute("missing");
          }
          return responseError;
        }
      });

    return client;
  }

  public post(url: string, postData: any, authObject?: any): any {

    let myAuth = this.authService ? this.authService : authObject;
    let myToken = "";
    if (myAuth) {
      myToken = myAuth.getAuthToken();
    }

    let self = this;
    let client = this.http
      .createRequest(url)
      .asPost().withContent(postData)
      .withHeader("AuthenticationToken", myToken)
      .withInterceptor({
        responseError(responseError) {
          console.log(responseError);
          if (responseError.statusCode === 401) {
            if (myAuth) {
              myAuth.destroySession();
            }
          }
          if (responseError.statusCode === 404) {
            self.router.navigateToRoute("missing");
          }
          return responseError;
        }
      });

    return client;
  }

}
import { Aurelia, inject } from 'aurelia-framework';
import HttpService from './httpservice';
import environment from './environment';
import { EventAggregator } from 'aurelia-event-aggregator';

@inject(EventAggregator, Aurelia, HttpService)
export default class Authservice {

  public http: HttpService = null;
  public app: Aurelia = null;
  public ea: EventAggregator = null;
  public authToken: any = null;

  private loginUrl: string = "";
  private logoutUrl: string = "";
  private checkUrl: string = "";

  constructor(eventAggregator, aurelia, httpService) {

    this.http = httpService;
    this.app = aurelia;
    this.ea = eventAggregator;

    this.loginUrl = "/login";
  }

  public getAuthToken() {
if (!sessionStorage[environment.tokenname] || 
   (sessionStorage[environment.tokenname] == null)) {
      return null;
   }
   return sessionStorage[environment.tokenname];
  }

  public login(loginName, password) {

    let postData = {
      loginName: loginName,
      password: password
    };

    let client = this.http.post(this.loginUrl, postData);

    client.send()
      .then((response) => response.content)
      .then((data) => {

        if (data.error) {
          this.ea.publish("loginMessage", { message: data.errorMessage });
          return;
        }

        if (data.authenticationFailed) {
          this.ea.publish("loginMessage", { message: "Invalid user name and/or password supplied." });
          return;
        }

        if (data.accountSuspended) {
          this.ea.publish("loginMessage", { message: "Your account has been suspended, please contact support." });
          return;
        }

        sessionStorage[environment.tokenname] = data.token;
        sessionStorage["displayedLoginName"] = data.displayName;
        location.assign('#/');
        this.app.setRoot('app');

      })
      .catch(() =>
      {
        debugger;
        alert("Something bad happened trying to connect to server.");
      });
  }

  public isAuthenticated() {
    // TODO: hook this up to check auth token validity via rest call???
    let token = this.getAuthToken();
    return token !== null;
  }

}

enum LoginStates {
  LoginValid = 0,
  BadUserNameOrPassword,
  AccountSuspended
   }
然后我在其他模块/类中使用它,如下所示:

import { inject } from 'aurelia-framework';
import { Router } from 'aurelia-router';
import { HttpClient } from 'aurelia-http-client';
import environment from './environment';

@inject(HttpClient, Router)
export default class httpservice {

  private http: HttpClient = null;
  private router: Router = null;
  private authService: any = null;
  private authToken: string = "";

  constructor(HttpClient, Router) {

    this.http = HttpClient;
    this.router = Router;

    HttpClient.configure(http => {
      http.withBaseUrl(environment.servicebase);
    });

  }

  public setAuthService(authService: any) {
    this.authService = authService;
  }

  public get(url: string, authObject?: any): any {

    let myAuth = this.authService ? this.authService : authObject;
    let myToken = "";
    if (myAuth) {
      myToken = myAuth.getAuthToken();
    }

    let self = this;
    let client = this.http
      .createRequest(url)
      .asGet()
      .withHeader("AuthenticationToken", myToken)
      .withInterceptor({
        responseError(responseError) {
          console.log(responseError);
          if (responseError.statusCode === 401) {
            if (myAuth) {
              myAuth.destroySession();
            }
          }
          if (responseError.statusCode === 404) {
            self.router.navigateToRoute("missing");
          }
          return responseError;
        }
      });

    return client;
  }

  public post(url: string, postData: any, authObject?: any): any {

    let myAuth = this.authService ? this.authService : authObject;
    let myToken = "";
    if (myAuth) {
      myToken = myAuth.getAuthToken();
    }

    let self = this;
    let client = this.http
      .createRequest(url)
      .asPost().withContent(postData)
      .withHeader("AuthenticationToken", myToken)
      .withInterceptor({
        responseError(responseError) {
          console.log(responseError);
          if (responseError.statusCode === 401) {
            if (myAuth) {
              myAuth.destroySession();
            }
          }
          if (responseError.statusCode === 404) {
            self.router.navigateToRoute("missing");
          }
          return responseError;
        }
      });

    return client;
  }

}
import { Aurelia, inject } from 'aurelia-framework';
import HttpService from './httpservice';
import environment from './environment';
import { EventAggregator } from 'aurelia-event-aggregator';

@inject(EventAggregator, Aurelia, HttpService)
export default class Authservice {

  public http: HttpService = null;
  public app: Aurelia = null;
  public ea: EventAggregator = null;
  public authToken: any = null;

  private loginUrl: string = "";
  private logoutUrl: string = "";
  private checkUrl: string = "";

  constructor(eventAggregator, aurelia, httpService) {

    this.http = httpService;
    this.app = aurelia;
    this.ea = eventAggregator;

    this.loginUrl = "/login";
  }

  public getAuthToken() {
if (!sessionStorage[environment.tokenname] || 
   (sessionStorage[environment.tokenname] == null)) {
      return null;
   }
   return sessionStorage[environment.tokenname];
  }

  public login(loginName, password) {

    let postData = {
      loginName: loginName,
      password: password
    };

    let client = this.http.post(this.loginUrl, postData);

    client.send()
      .then((response) => response.content)
      .then((data) => {

        if (data.error) {
          this.ea.publish("loginMessage", { message: data.errorMessage });
          return;
        }

        if (data.authenticationFailed) {
          this.ea.publish("loginMessage", { message: "Invalid user name and/or password supplied." });
          return;
        }

        if (data.accountSuspended) {
          this.ea.publish("loginMessage", { message: "Your account has been suspended, please contact support." });
          return;
        }

        sessionStorage[environment.tokenname] = data.token;
        sessionStorage["displayedLoginName"] = data.displayName;
        location.assign('#/');
        this.app.setRoot('app');

      })
      .catch(() =>
      {
        debugger;
        alert("Something bad happened trying to connect to server.");
      });
  }

  public isAuthenticated() {
    // TODO: hook this up to check auth token validity via rest call???
    let token = this.getAuthToken();
    return token !== null;
  }

}

enum LoginStates {
  LoginValid = 0,
  BadUserNameOrPassword,
  AccountSuspended
   }
请注意,我已经从auth库中删除了一些代码,以减少混淆

总的来说,所有这些都很有效。当401和404发生时,拦截器会被触发,如果我加上500,也会被处理,所以哪里都好

我遇到的问题是如何处理通信故障

正如您在登录例程中所看到的那样,我有一个问题需要解决

我预计,如果无法访问服务器或发生其他一些基本通信故障,则会触发此捕获,而不是“then”,从而允许我处理错误,但事实并非如此

取而代之的是控制台中的以下内容:

更糟糕的是,我的登录例程没有中止,它实际上成功了,并允许显示登录页面

似乎在库进行选项调用时(发生此错误时),没有考虑我的用户代码

成功的飞行前/ajax请求需要选项调用,因此阻止这种情况不是一个选项,我觉得如果选项调用没有中止,而是进入POST调用,那么我的错误处理将被考虑在内

无法捕获这样的错误似乎很愚蠢,特别是在当今的移动世界中,设备可能无法覆盖或暂时脱机

如果有人对如何解决这个问题有任何想法,我很乐意听听

更新1 我的问题似乎与此有关:


但是,我没有使用“useStandardConfiguration()”,这显然是造成这种情况的原因。我也没有使用fetch客户机,但是我注意到两个客户机中的API实际上是相同的,所以我想知道底层代码是否也类似。

好。。。。因此,经过一个漫长而艰难的下午的挠头和拔头发之后,事实证明,整个事情实际上与“蓝鸟承诺库”的一个报道问题有关,这就是aurelia用来管理其承诺的东西

有关蓝鸟问题的链接可在此处找到:

根据BB开发者的说法,这并不是一个特别的问题,但是对于许多遇到它的人来说,它看起来确实是一个问题

底线是,库的设计不是为了抛出由它直接生成的错误(如问题页面上的示例所示)

根据BB团队的说法,正确的方法是要么完全抛出一个新错误,要么从传递给promise的错误中派生一个新实例,并在重新抛出之前更改该实例的参数

当然,由于Aurelia中的抽象,这对我们大多数人来说都不是一个选项,除非我们想改变http客户机库代码

这方面的一些标记需要转到“蓝狐”以获取他/她的上述评论

最终的解决方案如下所示:

import { inject } from 'aurelia-framework';
import { Router } from 'aurelia-router';
import { HttpClient, Interceptor } from 'aurelia-http-client';
import environment from './environment';
import Debugger = require("_debugger");

@inject(HttpClient, Router)
export default class httpservice {

  private http: HttpClient = null;
  private router: Router = null;
  private authService: any = null;
  private authToken: string = "";
  private myInterceptors: Interceptor;

  constructor(HttpClient, Router) {

    this.http = HttpClient;
    this.router = Router;

    HttpClient.configure(http => {
      http.withBaseUrl(environment.servicebase);
      http.withInterceptor(new HttpInterceptors());
    });

  }

  public setAuthService(authService: any) {
    this.authService = authService;
  }

  public get(url: string, authObject?: any): any {

    let myAuth = this.authService ? this.authService : authObject;
    let myToken = "";
    if (myAuth) {
      myToken = myAuth.getAuthToken();
    }

    let client = this.http
      .createRequest(url)
      .asGet()
      .withHeader("AuthenticationToken", myToken);

    return client;
  }

  public post(url: string, postData: any, authObject?: any): any {

    let myAuth = this.authService ? this.authService : authObject;
    let myToken = "";
    if (myAuth) {
      myToken = myAuth.getAuthToken();
    }

    let self = this;

    let client = this.http
      .createRequest(url)
      .asPost().withContent(postData)
      .withHeader("AuthenticationToken", myToken);

    return client;
  }

}

class HttpInterceptors implements Interceptor {

  responceError(error)
  {

    if (error.statusCode === 0) {
      throw new Error("Could not contact server");
    }

    if (error.statusCode === 401) {
      // do auth handling here
    }

    if (error.statusCode === 404) {
      // do 404 handling here
    }

    return error;

  }
}
神奇之处在于连接到我的HttpService底部的HttpInterceptors类。您应该能够看到状态代码为0的检查,并且此处执行的实际操作是抛出新错误

正是抛出这个新错误的操作导致捕获对http客户机的实际调用中的“catch”

如果你没有在这一点上抛出,那么一切都会崩溃,你会看到我最初的问题帖子中的场景,抛出,你会抓住它并在用户代码中处理它


这种方法在aurelia fetch客户端中也很明显,因为它的工作方式与使用BlueBird promise库的工作方式大致相似。

这可能是一种解决方法,而不是解决方案,但我也遇到了同样的问题。为了解决这个问题,我使用了
response(){
拦截器(而不是
responseError(){
)。使用它,我可以检查响应的头并标记相同的状态代码(我使用了200-399之外的任何代码)是的,我整个下午都在尝试,但没有成功,我就是无法让Typescript编译器接受我的拦截器。我尝试了几十种方法,扩展类、类型转换对象、内联代码,结果都是一样的,它只是不断告诉我“请求”、“请求错误”、“响应”、“响应错误”无法分配给拦截器类型。(它让我发疯:-)您介意让我看看您的代码吗?