I have a very quirky api that can only handle a single request at a time. Therefore, I need to ensure that every time a request is made, it goes into a queue, and that queue is executed one request at a time, until it is empty.
Normally, I just use jQuery's built in queue, since the site is already using jQuery. However, I was not sure if I could somehow decorate the $http service, or wrap it in another service that returns one promise at a time, or something else.
Here is my solution for that: http://plnkr.co/edit/Tmjw0MCfSbBSgWRhFvcg
The idea is: each run of service add request to queue and return promise. When request to $http is finished resolve/refuse returned promise and execute next task from queue if any.
app.factory('srv', function($q,$http) {
var queue=[];
var execNext = function() {
var task = queue[0];
$http(task.c).then(function(data) {
queue.shift();
task.d.resolve(data);
if (queue.length>0) execNext();
}, function(err) {
queue.shift();
task.d.reject(err);
if (queue.length>0) execNext();
})
;
};
return function(config) {
var d = $q.defer();
queue.push({c:config,d:d});
if (queue.length===1) execNext();
return d.promise;
};
});
Looks quite simple :)
Building on Valentyn's great work above, I rolled this code into a standalone Angular (v1.2+) request/response interceptor. It will queue $http requests automatically without needing to rework your code to use srv() everywhere:
( function() {
'use strict';
angular.module( 'app' ).config( [ '$httpProvider', function( $httpProvider ) {
/**
* Interceptor to queue HTTP requests.
*/
$httpProvider.interceptors.push( [ '$q', function( $q ) {
var _queue = [];
/**
* Shifts and executes the top function on the queue (if any). Note this function executes asynchronously (with a timeout of 1). This
* gives 'response' and 'responseError' chance to return their values and have them processed by their calling 'success' or 'error'
* methods. This is important if 'success' involves updating some timestamp on some object which the next message in the queue relies
* upon.
*/
function _shiftAndExecuteTop() {
setTimeout( function() {
_queue.shift();
if ( _queue.length > 0 ) {
_queue[0]();
}
}, 1 );
}
return {
/**
* Blocks each request on the queue. If the first request, processes immediately.
*/
request: function( config ) {
var deferred = $q.defer();
_queue.push( function() {
deferred.resolve( config );
} );
if ( _queue.length === 1 ) {
_queue[0]();
}
return deferred.promise;
},
/**
* After each response completes, unblocks the next request.
*/
response: function( response ) {
_shiftAndExecuteTop();
return response;
},
/**
* After each response errors, unblocks the next request.
*/
responseError: function( responseError ) {
_shiftAndExecuteTop();
return $q.reject( responseError );
},
};
} ] );
} ] );
} )();
Richard: Your code works perfect but it also works with inner request like template or $templateProviders.
Here is solution to work only with external http requests
/**
* Interceptor to queue HTTP requests.
*/
$httpProvider.interceptors.push(['$q', function ($q) {
var _queue = [];
/**
* Executes the top function on the queue (if any).
*/
function _executeTop() {
if (_queue.length === 0) {
return;
}
_queue[0]();
}
return {
/**
* Blocks each request on the queue. If the first request, processes immediately.
*/
request: function (config) {
if (config.url.substring(0, 4) == 'http') {
var deferred = $q.defer();
_queue.push(function () {
deferred.resolve(config);
});
if (_queue.length === 1) {
_executeTop();
}
return deferred.promise;
} else {
return config;
}
},
/**
* After each response completes, unblocks the next request.
*/
response: function (response) {
if (response.config.url.substring(0, 4) == 'http') {
_queue.shift();
_executeTop();
}
return response;
},
/**
* After each response errors, unblocks the next request.
*/
responseError: function (responseError) {
if (responseError.config.url.substring(0, 4) == 'http') {
_queue.shift();
_executeTop();
}
return $q.reject(responseError);
},
};
}]);
var queue = $q(function(resolve) { resolve(); });
function queuedHttp(httpConf) {
return queue = queue.then(function() {
return $http(httpConf);
});
}