How to prioritize requests in angular $http service?

若如初见. 提交于 2019-12-18 13:39:45

问题


I'm working on an application with a large amount of lazy data loading. I would like to prioritize http requests based on 'priority' param.

This is the concept of using it.

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

I was thinking of using $http interceptors. Something like that:

 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;

        }   
    };
});

But I can't return promise here. Any ideas?


回答1:


You can do this by making use of $http's timeout property, and use both request and responseError callbacks to save and execute each $http requests respectively.

Steps:

  1. Lazily inject the $http service within the request callback process, this will be the only way to get the $http service because injecting it in the factory's function causes circular dependency.

  2. Determine if the configuration passed in the request callback has been processed. If it has not been processed then add the configuration in the request stack and sort it by priority. Add a resolved promise in the timeout property of the configuration object, to cancel the current $http request. Finally return the configuration object.

  3. Once the $http request has been cancelled, catch it in the responseError callback. If there are items in the request stack, pop the first item(config) and invoke it using the lazy loaded $http service. Lastly return a rejected promise using the rejection parameter provided by the callback.

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);

  });

To make sure that each request has been invoked in the right order, you can check the logs in the console tab or the requests in the network tab.

Update:

If you want your requests invoked in order (when the first request must finish before the next request invokes) then you can tweak my solution in the responseError callback to something like this:

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);
}

UPDATE 06/16/2019

As mentioned in the comments, the promise returned by prioritized requests do not return the expected promise resolution or rejection. I have updated the interceptor to accommodate such scenario by:

  1. Saving a deferred promise relative to each http config.
  2. Return the deferred promise in the responseError interceptor for sake keeping the resolution or rejection of the request.
  3. Finally use the deferred promise in the iteration of prioritized requests.

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);

  });



回答2:


Try to wrap your timeout

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;

Seems like it's getting lost on $interval. aswell your deferred was instanced globaly set a var before.




回答3:


This was not the right solution. You can achieve this by writing your own service to prioritize your api calls queue before calling http get .

This will not work for the following use case Angular Http Priority



来源:https://stackoverflow.com/questions/28450443/how-to-prioritize-requests-in-angular-http-service

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!