【问题标题】:How to prioritize requests in angular $http service?如何在 Angular $http 服务中优先处理请求?
【发布时间】:2015-04-11 14:14:27
【问题描述】:

我正在开发一个具有大量延迟数据加载的应用程序。我想根据“优先级”参数对 http 请求进行优先级排序。

这是使用它的概念。

$http.get(url, {params: query, priority: 1})

我正在考虑使用 $http 拦截器。类似的东西:

 angular.module('myModule')
.factory('httpPriorityInterceptor', function ($interval, $q) {
    var requestStack = [];

    return {
        request: function (config) {

            config.priority = config.priority || 3;

            requestStack.push(config);
            requestStack.sort(sortByPriority);

            if (isFirstToGo(item)) return requestStack.pop();

            deferred = $q.defer();

            var intervalPromise = $interval(function(){

                if (isFirstToGo(item)) {
                    deferred.resolve(requestStack.pop());
                    $interval.cancel(intervalPromise);
                };

            }, 100);

            return deferred.promise;

        }   
    };
});

但我不能在这里返回承诺。有任何想法吗?

【问题讨论】:

    标签: angularjs angular-http angular-http-interceptors


    【解决方案1】:

    您可以通过使用$http 的超时属性来做到这一点,并使用requestresponseError 回调分别保存和执行每个$http 请求。

    步骤:

    1. request回调过程中延迟注入$http服务,这将是获得$http服务的唯一方法,因为在工厂函数中注入它会导致循环依赖。

    2. 确定在request 回调中传递的配置是否已被处理。如果尚未处理,则将配置添加到请求堆栈中并按优先级排序。在配置对象的 timeout 属性中添加已解析的 promise,以取消当前的 $http 请求。最后返回配置对象。

    3. 一旦$http 请求被取消,在responseError 回调中捕获它。如果请求堆栈中有项目,则弹出第一项(配置)并使用延迟加载的$http 服务调用它。最后使用回调提供的拒绝参数返回一个被拒绝的承诺。

    DEMO

    angular.module('demo', [])
    
      .config(function($httpProvider) {
        $httpProvider.interceptors.push('httpPriorityInterceptor');
      })
    
      .factory('httpPriorityInterceptor', function($q, $injector) {
    
    
        var requestStack = [], // request stack
            $http = null; // http service to be lazy loaded
    
        return {
          request: request, // request callback
          responseError: responseError // responseError callback
        };
    
        // comparison function to sort request stack priority
        function sort(config1, config2) {
          return config1.priority < config2.priority;
        }
    
        function request(config) {
    
          // Lazy load $http service
          if(!$http) {
            $http = $injector.get('$http');
          }
    
          // check if configuration has not been requested
          if(!config.hasBeenRequested) {
    
            // set indicator that configuration has been requested
            config.hasBeenRequested = true;
    
            // set default priority if not present
            config.priority = config.priority || 3;
    
            // add a copy of the configuration
            // to prevent it from copying the timeout property
            requestStack.push(angular.copy(config));
    
            // sort each configuration by priority
            requestStack = requestStack.sort(sort);
    
            // cancel request by adding a resolved promise
            config.timeout = $q.when();
          }
    
          // return config
          return config;
        }
    
    
        function responseError(rejection) {
    
          // check if there are requests to be processed
          if(requestStack.length > 0) {
    
            // pop the top most priority
            var config = requestStack.pop();
            console.log(config);
    
            // process the configuration
            $http(config);
          }
    
          // return rejected request
          return $q.reject(rejection);
        }
    
      })
    
      .run(function($http) {
    
        // create http request
        var createRequest = function(priority) {
          $http.get('/priority/' + priority, {priority: priority});
        };
    
        createRequest(3);
        createRequest(1);
        createRequest(4);
        createRequest(2);
    
      });
    

    为确保以正确的顺序调用每个请求,您可以检查控制台选项卡中的日志或网络选项卡中的请求。

    更新:

    如果您希望按顺序调用请求(当第一个请求必须在下一个请求调用之前完成时),那么您可以在 responseError 回调中调整我的解决方案,如下所示:

    DEMO

    function responseError(rejection) {
    
      // check if there are requests to be processed
      if(requestStack.length > 0) {
    
        requestStack.reduceRight(function(promise, config) {
          return promise.finally(function() {
            return $http(config);
          });
        }, $q.when());
    
        requestStack.length = 0;
    
      }
    
      // return rejected request
      return $q.reject(rejection);
    }
    

    2019 年 6 月 16 日更新

    如 cmets 中所述,优先请求返回的承诺不会返回预期的承诺解决或拒绝。我已经通过以下方式更新了拦截器以适应这种情况:

    1. 保存与每个 http 配置相关的延迟承诺。
    2. responseError 拦截器中返回延迟承诺,以保持请求的解决或拒绝。
    3. 最终在优先请求的迭代中使用延迟承诺。

    DEMO

    angular.module('demo', [])
    
      .config(function($httpProvider) {
        $httpProvider.interceptors.push('httpPriorityInterceptor');
      })
    
      .factory('httpPriorityInterceptor', function($q, $injector) {
    
    
        var requestStack = [], // request stack
            $http = null; // http service to be lazy loaded
    
        return {
          request: request, // request callback
          responseError: responseError // responseError callback
        };
    
        // comparison function to sort request stack priority
        function sort(config1, config2) {
          return config1.priority < config2.priority;
        }
    
        function request(config) {
    
          // Lazy load $http service
          if(!$http) {
            $http = $injector.get('$http');
          }
    
          // check if configuration has not been requested
          if(!config.hasBeenRequested) {
    
            // set indicator that configuration has been requested
            config.hasBeenRequested = true;
    
            // set default priority if not present
            config.priority = config.priority || 3;
    
            // add a defered promise relative to the config requested
            config.$$defer = $q.defer();
    
            // add a copy of the configuration
            // to prevent it from copying the timeout property
            requestStack.push(angular.copy(config));
    
            // sort each configuration by priority
            requestStack = requestStack.sort(sort);
    
            // cancel request by adding a resolved promise
            config.timeout = $q.when();
          }
    
          // return config
          return config;
        }
    
    
        function responseError(rejection) {
    
          // check if there are requests to be processed
          if(requestStack.length > 0) {
    
            requestStack.reduceRight(function(promise, config) {
              var defer = config.$$defer;
              delete config.$$defer;
              return promise.finally(function() {
                return $http(config)
                  .then(function(response) {
                    defer.resolve(response);
                  })
                  .catch(function(error) {
                    defer.reject(error);
                  });
    
              });
            }, $q.when());
    
            requestStack.length = 0;
    
          }
    
          return rejection.config.$$defer.promise;
        }
    
      })
    
      .run(function($http) {
    
        // create http request
        var createRequest = function(priority) {
          return $http.get(priority + '.json', {priority: priority});
        };
    
        createRequest(3);
        createRequest(1).then(function(data) { console.log(data); })
        createRequest(4);
        createRequest(2);
    
      });
    

    【讨论】:

    • 这就是我正在寻找的解决方案。我将尝试实施它。谢谢!
    • ryeballar 我喜欢您的回答,但是您在复制后放入请求堆栈中的承诺似乎并未执行先前承诺的代码。真正更优雅的解决方案是在您将请求放入堆栈时推迟执行,但我找不到这样做的好方法。
    • 您能否为这种情况提供等效的 plnkr 或 jsfiddle?
    • 我试过实现这个。但不幸的是,它发出了两个请求,其中一个被取消了。有什么解决办法吗?? @ryeballar
    • 这肯定行不通。如果你取消一个请求,那么你就不能正确地与 Promise 交互。例如,如果您有createRequest(1).then(() =&gt; { console.log(response); });,它将永远不会被命中,因为第一个请求被取消并且您在 responseError 方法中创建的第二个请求与第一个请求无关
    【解决方案2】:

    这不是正确的解决方案。您可以通过编写自己的服务来实现此目的,以便在调用 http get 之前优先考虑您的 api 调用队列。

    这不适用于以下用例 Angular Http Priority

    【讨论】:

      【解决方案3】:

      尝试结束你的超时

      var deferred = $q.defer();
             (function (_deferred){
              var intervalPromise = $interval(function(){
      
                  if (isFirstToGo(item)) {
                      _defferred.resolve(requestStack.pop());
                      $interval.cancel(intervalPromise);
                  };
      
              }, 100);
              })(deferred);
      return deferred.promise;
      

      似乎在 $interval 上迷路了。以及你的 deferred 被实例化 globaly 之前设置了 var

      【讨论】:

      • 是的,你是对的。但正如我所提到的,问题是我不能在这里返回承诺。它只接受 'config' 对象。
      • 在 $http.get 方法中挖掘了一点,看不到设置优先级的机会。也许在调用 $http.get 之前创建一个自己的服务来处理这个和优先级? service.httpGet(url,{priority:1});?
      猜你喜欢
      • 1970-01-01
      • 2013-04-28
      • 1970-01-01
      • 1970-01-01
      • 2015-08-08
      • 1970-01-01
      • 2017-05-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多