使用AngularJS的拦截器

拦截器

使用拦截器可以对请求进行预处理,在将请求传递给服务器之前,也可以对应用程序代码进行处理,并且还可以定义全局的错误处理。
https://docs.angularjs.org/api/ng/service/$http #拦截器

我尝试使用拦截器来测量从请求到响应的时间,并处理响应中的错误。

├── angular
│   ├── angular-cookies.js
│   ├── angular-resource.js
│   ├── angular-route.js
│   ├── angular-sanitize.js
│   └── angular.js
├── app
│   ├── controllers.js
│   ├── services.js
│   └── configurations.js ← 新規追加
└── index.html

从AngularJS的开发者指南中借用了示例源代码(用于将通过Yahoo Finance API获取的各国货币汇率应用于输入的数量和金额,并以列表形式显示)。参考链接:https://docs.angularjs.org/guide/concepts

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Try Interceptor</title>
    <script src="angular/angular.js"></script>
    <script src="angular/angular-resource.js"></script>
    <script src="angular/angular-cookies.js"></script>
    <script src="angular/angular-sanitize.js"></script>
    <script src="angular/angular-route.js"></script>
    <script src="app/app.js"></script>
    <script src="app/controllers.js"></script>
    <script src="app/services.js"></script>
  </head>
  <body ng-app="myapp.controllers">
    <h1>AngularJs Sample</h1>
    <div class="container" ng-controller="SampleController">
      <b>Invoice:</b>
      <div>
        Quantity: <input type="number" ng-model="qty" required />
      </div>
      <div>
        Costs: <input type="number" ng-model="cost" required /><br />
        <select ng-model="inCurr">
          <option ng-repeat="c in currencies">{{c}}</option>
        </select>
      </div>
      <div>
        <b>Total:</b>
        <span ng-repeat="c in currencies">
          {{total(c) | currency:c }}
        </span><br/>
        <button class="btn" ng-click="pay()">Pay</button>
      </div>
    </div>
</body>
</html>
angular.module('myapp.controllers', ['myapp.services'])
.controller('SampleController', function($scope, SampleService) {
  $scope.qty = 1;
  $scope.cost = 2;
  $scope.inCurr = 'JPY';
  $scope.currencies = SampleService.currencies;
  $scope.total = function (outCurr) {
    return SampleService.convert($scope.qty * $scope.cost, $scope.inCurr, outCurr);
  };
  $scope.pay = function () {
    SampleService.pay($scope.qty, $scope.incurr);
  };
});
angular.module('myapp.services', [])
.factory('SampleService', function ($http) {
  var YAHOO_FINANCE_URL_PATTERN = 
    'http://query.yahooapis.com/v1/public/yql?q=select * from '+
      'yahoo.finance.xchange where pair in ("PAIRS")&format=json&'+
        'env=store://datatables.org/alltableswithkeys&callback=JSON_CALLBACK';
  var currencies = ['USD', 'EUR', 'CNY', 'JPY'];
  var usdToForeignRates = {};
  refresh();
  return {
    currencies: currencies,
    convert: convert,
    refresh: refresh
  };
  function convert(amount, inCurr, outCurr) {
    return amount * usdToForeignRates[outCurr] * 1 / usdToForeignRates[inCurr];
  };

  function refresh() {
    var url = YAHOO_FINANCE_URL_PATTERN.replace('PAIRS', 'USD' + currencies.join('","USD'));
    return $http.jsonp(url).success(
      function(data, status, headers, config) {
        var time = config.responseTimestamp - config.requestTimestamp;
        console.log('The request took ' + (time / 1000) + ' seconds.');
        var newUsdToForeignRates = {};
        angular.forEach(data.query.results.rate, function(rate) {
          var currency = rate.id.substring(3,6);
          newUsdToForeignRates[currency] = window.parseFloat(rate.Rate);
        });
        usdToForeignRates = newUsdToForeignRates;
      }
    );
  };

  function pay(qty, incurr) {
    $http.post("/test", {qty: qty, incurr: incurr});
  }
});

在configurations.js中进行了Interceptor的处理。

angular.module('myapp.configurations', ['myapp.controllers'])
.config(function($httpProvider) {
  $httpProvider.interceptors.push(
    function ($q, $rootScope) {
      return {
        request: function(config) {
          config.requestTimestamp = new Date().getTime();
          return config;
        },
        response: function(response) {
          response.config.responseTimestamp = new Date().getTime();
          return response;
        },
        responseError: function(rejection) {
          if (500 == rejection.status) {
            alert('System Error!');
          }
          return $q.reject(rejection);
        }
      };
    }
  );
});

只需将工厂函数推送到$httpProvider拥有的interceptors数组中,即可定义Interceptor。

可以在拦截器Push的工厂中定义以下四个函数。

request : サーバに引き渡される前のリクエストの前処理を定義

response : リクエストを行ったアプリケーションコードに引き渡される前のレスポンスの前処理

requestError : リクエストでエラーが起きた際の処理を定義

responseError : レスポンスでエラーが起きた際の処理を定義

请参考AngularJS文档中的”https://docs.angularjs.org/api/ng/service/$http#Interceptors”部分。

为了实现测量请求到响应时间和处理响应错误,本文定义了request、response和responseError。

在请求和回复中计算处理时间,当响应错误为HTTP状态码500时执行处理。

试着去做

console

进行错误处理的测试

虽然可以在服务器端编写错误处理来进行确认,但这样比较麻烦,所以我打算通过测试来进行确认。

请参考以下内容来了解基本的测试编写方法。
使用Jasmine进行AngularJS测试。

AngularJS在它的$httpBackend中提供了模拟后端行为的功能,我们利用这个功能对SampleService的pay方法中的$http.post请求的服务器端进行了模拟。
※由于在加载SampleService时会使用$http.jsonp访问yahooapi,所以我们也对这个请求进行了模拟。

'use strict';

describe('Service: SampleService', function () {

  beforeEach(module('myapp.services'));

  var $httpBackend,
      SampleService ;

  var YAHOO_FINANCE_URL_PATTERN = 
    'http://query.yahooapis.com/v1/public/yql?q=select * from '+
      'yahoo.finance.xchange where pair in ("PAIRS")&format=json&'+
        'env=store://datatables.org/alltableswithkeys&callback=JSON_CALLBACK';

  var alert_msg;

  beforeEach(inject(function($injector) {

    // Set up the mock http service responses
    $httpBackend = $injector.get('$httpBackend');

    var currencies = ['USD', 'EUR', 'CNY', 'JPY'];
    var url = YAHOO_FINANCE_URL_PATTERN.replace('PAIRS', 'USD' + currencies.join('","USD'));
    $httpBackend.when('JSONP', url).respond(
      {query: {results: {rate: [
        {id: 'HOGE', rate: 1.0}
      ]}}}
    );

    alert_msg = '_defalut_';
    spyOn(window, 'alert').and.callFake(function(msg) {
      alert_msg = msg;
    });

    SampleService = $injector.get('SampleService');
  }));

  it('should success, when pay() is called', function() {
    expect(alert_msg).toEqual('_defalut_');
    var data = {qty: 1, cost: 1000};
    $httpBackend.when('POST', '/test', data).respond(200, '');
    SampleService.pay(1, 1000).success(function() {
       expect(true).toBe(true);
     }).error(function() {
       expect(false).toBe(true);
     });
    $httpBackend.flush();
    expect(alert_msg).toEqual('_defalut_');
  });

  it('should fail, when pay() is called', function() {
    expect(alert_msg).toEqual('_defalut_');
    var data = {qty: 1, cost: 1000};
    $httpBackend.when('POST', '/test', data).respond(400, '');

    SampleService.pay(1, 1000).success(function() {
      expect(false).toBe(true);
    }).error(function() {
      expect(true).toBe(true);
    });
    $httpBackend.flush();
    expect(alert_msg).toEqual('_defalut_');
  });

  it('should fail and emit alert message for status code 500, when pay() is called', function() {
    expect(alert_msg).toEqual('_defalut_');
    var data = {qty: 1, cost: 1000};
    $httpBackend.when('POST', '/test', data).respond(500, '');

    SampleService.pay(1, 1000).success(function() {
      expect(false).toBe(true);
    }).error(function() {
      expect(true).toBe(true);
    });
    $httpBackend.flush();
    expect(alert_msg).toEqual('System Error!');
  });
});

这个测试有三个案例。

    1. 200状态码的正常情况

 

    1. 400状态码的异常情况

 

    500状态码的异常情况

我正在检查警告消息的内容。

请参考

– AngularJS 拦截器及其有用示例
– 为了更好地使用 AngularJS 的提示和技巧

bannerAds