16

我正在使用 ES6 类构建和 AngularJS 应用程序,并将 traceur 转换为 AMD 格式的 ES5。

在我的模块中,我导入拦截器类并将其注册为服务,然后使用 module.config 中的 $httpProvider.interceptors 注册此服务:

var commonModule = angular.module(moduleName, [constants.name]);

import authenticationInterceptor from './authentication/authentication.interceptor';

commonModule.service('authenticationInterceptor', authenticationInterceptor);

commonModule.config( $httpProvider =>  {
    $httpProvider.interceptors.push('authenticationInterceptor');
});

我的拦截器类同时注入$q$window服务,将它们保存在构造函数中以备后用。我使用调试器跟踪了这一部分,并且注入正常进行:

'use strict';
/*jshint esnext: true */

var authenticationInterceptor = class AuthenticationInterceptor {

    /* ngInject */
    constructor($q, $window) {
        this.$q = $q;
        this.$window = $window;
    }

    responseError(rejection) {
        var authToken = rejection.config.headers.Authorization;
        if (rejection.status === 401 && !authToken) {
            let authentication_url = rejection.data.errors[0].data.authenticationUrl;
            this.$window.location.replace(authentication_url);
            return this.$q.defer(rejection);
        }
        return this.$q.reject(rejections);
    }
}

authenticationInterceptor.$inject = ['$q', '$window'];

export default authenticationInterceptor;

当我发出一个以401响应的请求时,拦截器会适当地触发,但在“responseError”方法中,“this”变量指向窗口对象而不是我的拦截器,因此我无权访问this.$q这个.$窗口

我不知道为什么?有任何想法吗?

4

9 回答 9

27

正如alexpods指出的那样,上下文 ( this) 丢失了,因为 Angular 框架只保留对处理程序函数本身的引用,并在没有任何上下文的情况下直接调用它们。

我最近写了一篇关于$http使用 TypeScript 编写拦截器的博客文章,它也适用于 ES6 类:AngularJS 1.x Interceptors Using TypeScript

总结一下我在这篇文章中讨论的内容,为了不丢失this处理程序,您必须将方法定义为箭头函数,有效地将函数直接放在constructor已编译的 ES5 代码中的类函数中。

class AuthenticationInterceptor {

    /* ngInject */
    constructor($q, $window) {
        this.$q = $q;
        this.$window = $window;
    }

    responseError = (rejection) => {
        var authToken = rejection.config.headers.Authorization;
        if (rejection.status === 401 && !authToken) {
            let authentication_url = rejection.data.errors[0].data.authenticationUrl;
            this.$window.location.replace(authentication_url);
            return this.$q.defer(rejection);
        }
        return this.$q.reject(rejections);
    }
}

如果你真的坚持将你的拦截器写成一个完全基于原型的类,你可以为你的拦截器定义一个基类并扩展它。基类将用实例方法替换原型拦截器函数,因此我们可以这样编写拦截器:

class HttpInterceptor {
  constructor() {
    ['request', 'requestError', 'response', 'responseError']
        .forEach((method) => {
          if(this[method]) {
            this[method] = this[method].bind(this);
          }
        });
  }
}

class AuthenticationInterceptor extends HttpInterceptor {

    /* ngInject */
    constructor($q, $window) {
        super();
        this.$q = $q;
        this.$window = $window;
    }

    responseError(rejection) {
        var authToken = rejection.config.headers.Authorization;
        if (rejection.status === 401 && !authToken) {
            let authentication_url = rejection.data.errors[0].data.authenticationUrl;
            this.$window.location.replace(authentication_url);
            return this.$q.defer(rejection);
        }
        return this.$q.reject(rejections);
    }
}
于 2015-12-08T18:27:32.313 回答
4

这与我遇到的问题完全相同,但是,我找到了一种解决方法,方法是在 self 变量中设置“this”,就像解决 es5 上的范围问题一样,并且效果很好:

let self;

class AuthInterceptor{

   constructor(session){
       self = this;
       this.session = session;
   }

   request(config){
       if(self.session) {
           config.headers = self.session.getSessionParams().headers; 
       }
       return config;
   }

   responseError(rejection){
       if(rejection.status == 401){

       }

       return rejection;
   }

}

export default AuthInterceptor;
于 2015-02-25T13:18:09.643 回答
4

看看这几行源代码

// apply interceptors
forEach(reversedInterceptors, function(interceptor) {
    if (interceptor.request || interceptor.requestError) {
        chain.unshift(interceptor.request, interceptor.requestError);
    }
    if (interceptor.response || interceptor.responseError) {
        chain.push(interceptor.response, interceptor.responseError);
    }
});

interceptor.responseError方法被推入链中时,它会失去其上下文(只是函数被推入,没有任何上下文);

稍后它将作为拒绝回调添加到 Promise 中:

while (chain.length) {
    var thenFn = chain.shift();
    var rejectFn = chain.shift();

    promise = promise.then(thenFn, rejectFn);
}

因此,如果 promise 被拒绝,rejectFn(您的responseError函数)将作为普通函数执行。在这种情况下,如果脚本以非严格模式执行,则this引用,否则就等于。windownull

恕我直言,Angular 1 是在考虑 ES5 的情况下编写的,所以我认为将它与 ES6 一起使用并不是一个好主意。

于 2015-02-20T23:54:36.623 回答
4

要添加到对话中,您可以从包含显式绑定的类方法的构造函数返回一个对象。

export default class HttpInterceptor {

   constructor($q, $injector) {
       this.$q = $q;
       this.$injector = $injector;

       return {
           request: this.request.bind(this),
           requestError: this.requestError.bind(this),
           response: this.response.bind(this),
           responseError: this.responseError.bind(this)
       }
   }

   request(req) {
       this.otherMethod();
       // ...
   }

   requestError(err) {
       // ...
   }

   response(res) {
       // ...
   }

   responseError(err) {
       // ...
   }

   otherMethod() {
       // ...
   }

}
于 2016-05-05T16:04:50.407 回答
1

请注意,在类属性中使用箭头函数是 ES7 的一项实验性功能。然而,大多数转译器对此没有问题。

如果你想坚持官方的 ES6 实现,你可以通过在构造函数中定义你的方法来创建实例方法而不是原型方法。

class AuthenticationInterceptor {
  /* ngInject */
  constructor($q, $window) {
    
    this.responseError = (rejection) => {
      const authToken = rejection.config.headers.Authorization;
      if (rejection.status === 401 && !authToken) {
        const authentication_url = rejection.data.errors[0].data.authenticationUrl;
        $window.location.replace(authentication_url);
        return $q.defer(rejection);
      }
      return $q.reject(rejection);
    };
    
  }
}

我喜欢这个解决方案,因为它减少了样板代码的数量;

  • 您不再需要将所有依赖项放入this. 因此,this.$q您可以只使用$q.
  • 无需从构造函数返回显式绑定的类方法

有一个额外的缩进级别是一个缺点。此外,这种方法可能不适合实例化很多的类,因为在这种情况下它会消耗更多的内存。例如; 使用直接类属性(转换为原型方法)对于可能在一页上多次使用的组件控制器更有效。不要担心服务、提供者和工厂,因为它们都是单例的,它们只会被实例化一次。

于 2016-07-06T13:08:29.010 回答
0

具有箭头功能的工作解决方案:

var AuthInterceptor = ($q, $injector, $log) => {
    'ngInject';

    var requestErrorCallback = request => {
        if (request.status === 500) {
          $log.debug('Something went wrong.');
        }
        return $q.reject(request);
    };

    var requestCallback = config => {
        const token = localStorage.getItem('jwt');

        if (token) {
            config.headers.Authorization = 'Bearer ' + token;
        }
        return config;
    };

    var responseErrorCallback = response => {
         // handle the case where the user is not authenticated
        if (response.status === 401 || response.status === 403) {
            // $rootScope.$broadcast('unauthenticated', response);
            $injector.get('$state').go('login');
       }
       return $q.reject(response);
    }

  return {
    'request':       requestCallback,
    'response':      config => config,
    'requestError':  requestErrorCallback,
    'responseError': responseErrorCallback,
  };
};

/***/
var config = function($httpProvider) {
    $httpProvider.interceptors.push('authInterceptor');
};

/***/    
export
default angular.module('services.auth', [])
    .service('authInterceptor', AuthInterceptor)
    .config(config)
    .name;
于 2015-09-29T23:46:14.997 回答
0

我不使用ngInject的工作解决方案

myInterceptor.js

export default ($q) => {
let response = (res) => {
    return res || $q.when(res);
}

let responseError = (rejection) => {
    //do your stuff HERE!!
    return $q.reject(rejection);
}

return {
    response: response,
    responseError: responseError
}

}

我的AngularApp.js

// angular services
import myInterceptor from 'myInterceptor';

// declare app
const application = angular.module('myApp', [])
        .factory('$myInterceptor', myInterceptor)
        .config(['$httpProvider', function($httpProvider) {  
           $httpProvider.interceptors.push('$myInterceptor');
        }]);
于 2016-11-25T14:06:58.783 回答
0

为了compelement其他关于箭头函数的好答案,我认为在拦截器中使用静态工厂方法会更干净一些:

export default class AuthenticationInterceptor {
 static $inject = ['$q', '$injector', '$rootRouter'];
 constructor ($q, $injector, $rootRouter) {
  this.$q = $q;
  this.$injector = $injector;
  this.$rootRouter = $rootRouter;
 }

 static create($q, $injector, $rootRouter) {
  return new AuthenticationInterceptor($q, $injector, $rootRouter);
 }

 responseError = (rejection) => {
  const HANDLE_CODES = [401, 403];

  if (HANDLE_CODES.includes(rejection.status)) {
   // lazy inject in order to avoid circular dependency for $http
   this.$injector.get('authenticationService').clearPrincipal();
   this.$rootRouter.navigate(['Login']);
  }
  return this.$q.reject(rejection);
 }
}

用法:

.config(['$provide', '$httpProvider', function ($provide, $httpProvider) {
$provide.factory('reauthenticationInterceptor', AuthenticationInterceptor.create);
$httpProvider.interceptors.push('reauthenticationInterceptor');
}]);
于 2016-04-13T07:23:34.993 回答
-1
export default class AuthInterceptor{


    /*@ngInject;*/
    constructor(SomeService,$q){

        this.$q=$q;
        this.someSrv = SomeService;



        this.request = (config) =>{
            ...
            this.someSrv.doit();
            return config;

        }

        this.response = (response)=>{
            ...
            this.someSrv.doit();
            return response;
        }

        this.responseError = (response) => {
           ...
           return this.$q.reject(response);
        }



    }



}
于 2016-09-14T09:29:44.803 回答