Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

fix($http): immediatelly increment $browser's outstandingRequestCount #13869

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 26 additions & 11 deletions src/ng/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -375,8 +375,8 @@ function $HttpProvider() {
**/
var interceptorFactories = this.interceptors = [];

this.$get = ['$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector',
function($httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector) {
this.$get = ['$browser', '$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector',
function($browser, $httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector) {

var defaultCache = $cacheFactory('$http');

Expand Down Expand Up @@ -955,25 +955,23 @@ function $HttpProvider() {
return sendReq(config, reqData).then(transformResponse, transformResponse);
};

var chain = [serverRequest, undefined];
var requestInterceptors = [];
var responseInterceptors = [];
var promise = $q.when(config);

// apply interceptors
forEach(reversedInterceptors, function(interceptor) {
if (interceptor.request || interceptor.requestError) {
chain.unshift(interceptor.request, interceptor.requestError);
requestInterceptors.unshift(interceptor.request, interceptor.requestError);
}
if (interceptor.response || interceptor.responseError) {
chain.push(interceptor.response, interceptor.responseError);
responseInterceptors.push(interceptor.response, interceptor.responseError);
}
});

while (chain.length) {
var thenFn = chain.shift();
var rejectFn = chain.shift();

promise = promise.then(thenFn, rejectFn);
}
promise = chainInterceptors(promise, requestInterceptors).then(serverRequest);
promise.finally(completeOutstandingRequest);
promise = chainInterceptors(promise, responseInterceptors);

if (useLegacyPromise) {
promise.success = function(fn) {
Expand All @@ -998,6 +996,8 @@ function $HttpProvider() {
promise.error = $httpMinErrLegacyFn('error');
}

$browser.$$incOutstandingRequestCount();

return promise;

function transformResponse(response) {
Expand All @@ -1010,6 +1010,21 @@ function $HttpProvider() {
: $q.reject(resp);
}

function chainInterceptors(promise, interceptors) {
while (interceptors.length) {
var thenFn = interceptors.shift();
var rejectFn = interceptors.shift();

promise = promise.then(thenFn, rejectFn);
}

return promise;
}

function completeOutstandingRequest() {
$browser.$$completeOutstandingRequest(noop);
}

function executeHeaderFns(headers, config) {
var headerContent, processedHeaders = {};

Expand Down
2 changes: 0 additions & 2 deletions src/ng/httpBackend.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ function $HttpBackendProvider() {
function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) {
// TODO(vojta): fix the signature
return function(method, url, post, callback, headers, timeout, withCredentials, responseType) {
$browser.$$incOutstandingRequestCount();
url = url || $browser.url();

if (lowercase(method) == 'jsonp') {
Expand Down Expand Up @@ -158,7 +157,6 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
jsonpDone = xhr = null;

callback(status, response, headersString, statusText);
$browser.$$completeOutstandingRequest(noop);
}
};

Expand Down
171 changes: 171 additions & 0 deletions test/ng/httpSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1869,6 +1869,177 @@ describe('$http', function() {
});


describe('$browser\'s outstandingRequestCount', function() {
var $http;
var $httpBackend;
var $rootScope;
var incOutstandingRequestCountSpy;
var completeOutstandingRequestSpy;


describe('without interceptors', function() {
beforeEach(setupServicesAndSpies);


it('should immediately call `$browser.$$incOutstandingRequestCount()`', function() {
expect(incOutstandingRequestCountSpy).not.toHaveBeenCalled();
$http.get('');
expect(incOutstandingRequestCountSpy).toHaveBeenCalledOnce();
});


it('should call `$browser.$$completeOutstandingRequest()` upon response', function() {
$httpBackend.when('GET').respond(200);

expect(completeOutstandingRequestSpy).not.toHaveBeenCalled();
$http.get('');
expect(completeOutstandingRequestSpy).not.toHaveBeenCalled();
$httpBackend.flush();
expect(completeOutstandingRequestSpy).toHaveBeenCalledOnce();
});


it('should call `$browser.$$completeOutstandingRequest()` upon error', function() {
$httpBackend.when('GET').respond(500);

expect(completeOutstandingRequestSpy).not.toHaveBeenCalled();
$http.get('');
expect(completeOutstandingRequestSpy).not.toHaveBeenCalled();
$httpBackend.flush();
expect(completeOutstandingRequestSpy).toHaveBeenCalledOnce();
});


it('should properly increment/decrement `outstandingRequestCount` '
+ 'upon error in transformRequest',
inject(function($exceptionHandler) {
expect(incOutstandingRequestCountSpy).not.toHaveBeenCalled();
expect(completeOutstandingRequestSpy).not.toHaveBeenCalled();

$http.get('', {transformRequest: function() { throw new Error(); }});

expect(incOutstandingRequestCountSpy).toHaveBeenCalledOnce();
expect(completeOutstandingRequestSpy).not.toHaveBeenCalled();

$rootScope.$digest();

expect(incOutstandingRequestCountSpy).toHaveBeenCalledOnce();
expect(completeOutstandingRequestSpy).toHaveBeenCalledOnce();

$exceptionHandler.errors = [];
})
);


it('should properly increment/decrement `outstandingRequestCount` '
+ 'upon error in transformResponse',
inject(function($exceptionHandler) {
expect(incOutstandingRequestCountSpy).not.toHaveBeenCalled();
expect(completeOutstandingRequestSpy).not.toHaveBeenCalled();

$httpBackend.when('GET').respond(200);
$http.get('', {transformResponse: function() { throw new Error(); }});

expect(incOutstandingRequestCountSpy).toHaveBeenCalledOnce();
expect(completeOutstandingRequestSpy).not.toHaveBeenCalled();

$httpBackend.flush();

expect(incOutstandingRequestCountSpy).toHaveBeenCalledOnce();
expect(completeOutstandingRequestSpy).toHaveBeenCalledOnce();

$exceptionHandler.errors = [];
})
);
});


describe('with interceptors', function() {
var requestInterceptorCalled;
var responseInterceptorCalled;


beforeEach(module(function($httpProvider) {
requestInterceptorCalled = false;
responseInterceptorCalled = false;

$httpProvider.interceptors.push(function($q) {
return {
request: function(config) {
requestInterceptorCalled = true;
return config._requestError ? $q.reject() : config;
},
response: function() {
responseInterceptorCalled = true;
return $q.reject();
}
};
});
}));
beforeEach(setupServicesAndSpies);


it('should properly increment/decrement `outstandingRequestCount` '
+ 'upon error in request interceptor',
function() {
expect(incOutstandingRequestCountSpy).not.toHaveBeenCalled();
expect(completeOutstandingRequestSpy).not.toHaveBeenCalled();
expect(requestInterceptorCalled).toBe(false);

$http.get('', {_requestError: true});

expect(incOutstandingRequestCountSpy).toHaveBeenCalledOnce();
expect(completeOutstandingRequestSpy).not.toHaveBeenCalled();
expect(requestInterceptorCalled).toBe(false);

$rootScope.$digest();

expect(incOutstandingRequestCountSpy).toHaveBeenCalledOnce();
expect(completeOutstandingRequestSpy).toHaveBeenCalledOnce();
expect(requestInterceptorCalled).toBe(true);
}
);


it('should properly increment/decrement `outstandingRequestCount` '
+ 'upon error in response interceptor',
function() {
expect(incOutstandingRequestCountSpy).not.toHaveBeenCalled();
expect(completeOutstandingRequestSpy).not.toHaveBeenCalled();
expect(responseInterceptorCalled).toBe(false);

$httpBackend.when('GET').respond(200);
$http.get('', {_requestError: false});

expect(incOutstandingRequestCountSpy).toHaveBeenCalledOnce();
expect(completeOutstandingRequestSpy).not.toHaveBeenCalled();
expect(responseInterceptorCalled).toBe(false);

$httpBackend.flush();

expect(incOutstandingRequestCountSpy).toHaveBeenCalledOnce();
expect(completeOutstandingRequestSpy).toHaveBeenCalledOnce();
expect(responseInterceptorCalled).toBe(true);
}
);
});


function setupServicesAndSpies() {
inject(function($browser, _$http_, _$httpBackend_, _$rootScope_) {
$http = _$http_;
$httpBackend = _$httpBackend_;
$rootScope = _$rootScope_;

incOutstandingRequestCountSpy
= spyOn($browser, '$$incOutstandingRequestCount').andCallThrough();
completeOutstandingRequestSpy
= spyOn($browser, '$$completeOutstandingRequest').andCallThrough();
});
}
});


it('should pass timeout, withCredentials and responseType', function() {
var $httpBackend = jasmine.createSpy('$httpBackend');

Expand Down