问题
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:
Lazily inject the
$http
service within therequest
callback process, this will be the only way to get the$http
service because injecting it in the factory's function causes circular dependency.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.Once the
$http
request has been cancelled, catch it in theresponseError
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:
- Saving a deferred promise relative to each http config.
- Return the deferred promise in the
responseError
interceptor for sake keeping the resolution or rejection of the request. - 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