【问题标题】:How to measure requests (XHR) duration in AngularJS?如何在 AngularJS 中测量请求(XHR)持续时间?
【发布时间】:2016-12-30 06:57:36
【问题描述】:

我正在尝试为 AngularJS (1.5.x) 实现一个拦截器,它可以测量每个 api 调用并将每个调用的持续时间发送到 Google Analytics。

我从一个虚拟实现开始,只使用new Date().getTime()

(function (angular) {
  'use strict';

  angular.module('MODULE')
    .factory('HttpLoadingInterceptor', HttpLoadingInterceptor)
    .config(HttpLoadingInterceptorConfig);

  HttpLoadingInterceptor.$inject = ['$injector', '$q', 'AnalyticsService', '_'];

  HttpLoadingInterceptorConfig.$inject = ['$httpProvider'];

  function HttpLoadingInterceptor ($injector, $q, AnalyticsService, _) {
    var REQUEST_GA_EVENT_NAME = 'REQUEST';

    return {
      request: request,
      requestError: requestError,
      response: response,
      responseError: responseError
    };

    function request (config) {
      config.requestTimestamp = now();
      return config || $q.when(config);
    }

    function requestError (rejection) {
      rejection.config.responseTimestamp = now();
      trackRequest(rejection);
      return $q.reject(rejection);
    }

    function response (response) {
      response.config.responseTimestamp = now();
      trackRequest(response);
      return response || $q.when(response);
    }

    function responseError (rejection) {
      rejection.config.responseTimestamp = now();
      trackRequest(rejection);
      return $q.reject(rejection);
    }

    function trackRequest (response) {
      if (!_.startsWith(response.config.url, 'api')) {
        return;
      }
      AnalyticsService.trackEvent(
        REQUEST_GA_EVENT_NAME,
        response.config.url,
        response.status,
        response.config.responseTimestamp - response.config.requestTimestamp
      );
    }

    function now () {
      return new Date().getTime();
    }
  }

  function HttpLoadingInterceptorConfig ($httpProvider) {
    $httpProvider.interceptors.push('HttpLoadingInterceptor');
  }
})(window.angular);

乍看之下看起来不错,但比较 Chrome 中的“网络”标签中收集的请求的持续时间,我注意到在我的代码中收集的请求的持续时间总是更高(有时很多!)来自 Chrome 收集的内容。

我想到的另一个想法是使用 User Navigation API,所以我将代码更改为:

(function (angular, performance) {
  'use strict';

  angular.module('MODULE')
    .factory('HttpLoadingInterceptor', HttpLoadingInterceptor)
    .config(HttpLoadingInterceptorConfig);

  HttpLoadingInterceptor.$inject = ['$injector', '$q', 'AnalyticsService', '_'];

  HttpLoadingInterceptorConfig.$inject = ['$httpProvider'];

  function HttpLoadingInterceptor ($injector, $q, AnalyticsService, _) {
    var REQUEST_GA_EVENT_NAME = 'REQUEST';
    var measureReqCnt = 1;

    return {
      request: request,
      requestError: requestError,
      response: response,
      responseError: responseError
    };

    function request (config) {
      if (shouldMeasure(config.url)) {
        measureRequest(config);
      }
      return config || $q.when(config);
    }

    function requestError (rejection) {
      if (shouldMeasure(rejection.config.url)) {
        trackRequest(rejection);
      }
      return $q.reject(rejection);
    }

    function response (response) {
      if (shouldMeasure(response.config.url)) {
        trackRequest(response);
      }
      return response || $q.when(response);
    }

    function responseError (rejection) {
      if (shouldMeasure(rejection.config.url)) {
        trackRequest(rejection);
      }
      return $q.reject(rejection);
    }

    function shouldMeasure (url) {
      return performance && _.startsWith(url, 'api');
    }

    function measureRequest (config) {
      config.measureName = [config.url, measureReqCnt++].join('_');
      config.markStartName = [config.measureName, 'start'].join('_');
      config.markEndName = [config.measureName, 'end'].join('_');
      performance.mark(config.markStartName);
    }

    function trackRequest (response) {
      performance.mark(response.config.markEndName);
      performance.measure(response.config.measureName, response.config.markStartName, response.config.markEndName);
      var entries = performance.getEntriesByName(response.config.measureName, 'measure');
      if (entries && entries.length) {
        AnalyticsService.trackEvent(
          REQUEST_GA_EVENT_NAME,
          response.config.url,
          response.status,
          entries[0].duration
        );
      }
    }
  }

  function HttpLoadingInterceptorConfig ($httpProvider) {
    $httpProvider.interceptors.push('HttpLoadingInterceptor');
  }
})(window.angular, window.performance);

.. 但我再次收到了不同于 Chrome 收集的结果,甚至不同于使用new Date().getTime()

测量的结果

我做错了什么?我该怎么做才对?也许是资源计时 API? AngularJS 确实强加了一点。

我无法使用用户导航 API 方法 - window.performacne.getEntries() 来查找我的请求持续时间,因为我无法识别特定请求。每个请求的参数在那里不可用,我有很多相同的 API 调用,只是参数不同。

我应该装饰 AngularJS 使用的原生 XHR 请求吗?

【问题讨论】:

    标签: javascript angularjs xmlhttprequest navigation-timing-api resource-timing-api


    【解决方案1】:

    利用 performance.now() 怎么样? (https://developer.mozilla.org/en-US/docs/Web/API/Performance/now),与 Date() 相比,它专为高精度性能跟踪而设计?

    见: performance.now() vs Date.now()

    另外,你关心绝对数字吗?对于性能跟踪,有时相对是最好的,因为比较苹果和橙子很少能给出真正的数据点。

    我的猜测是,使用的计时器和所用时间的点之间存在差异。 Chrome 开发工具可能更接近“在线”而不是包含应用程序代码。如果有帮助,我们还有一个 API 分析和调试工具 (www.moesif.com/features),但要充分披露我是 CEO。

    【讨论】:

    • 当然,我尝试了 Performance API 并且得到了相同的结果 == 没有有效结果。正如我与 Angular 团队确定的那样,Angular 会给我们自己的开销,所以用所有这些方法来衡量时间是无效的。
    • 如果你不想在你的数字中包含任何开销,我想你必须找到一种使用performance.getEntries()(或PerformanceObserver)的方法。顺便说一句,我不知道你对这个测量电话的确切目标,但你确定你不想要那个开销吗?因为,这不仅仅是框架开销,可能是因为您的响应太大并且解析 json 需要时间。我相信这是你应该衡量和关心的事情。
    猜你喜欢
    • 2020-10-24
    • 1970-01-01
    • 1970-01-01
    • 2012-08-09
    • 2016-04-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-15
    相关资源
    最近更新 更多