diff --git a/src/ng/httpBackend.js b/src/ng/httpBackend.js index 81f5fb8bf69d..1cc4f0c201a9 100644 --- a/src/ng/httpBackend.js +++ b/src/ng/httpBackend.js @@ -38,6 +38,7 @@ function $HttpBackendProvider() { function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { var ABORTED = -1; + var TIMEOUT = -2; // TODO(vojta): fix the signature return function(method, url, post, callback, headers, timeout, withCredentials, responseType) { @@ -68,6 +69,14 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc } }); + // The fake timeout mechanism relies on xhr.abort(), so it's necessary to do this so that the + // expected behaviour occurs. + xhr.onabort = function() { + if (status === undefined) { + status = ABORTED; + } + }; + // In IE6 and 7, this might be called synchronously when xhr.send below is called and the // response is in the cache. the promise api will ensure that to the app code the api is // always async @@ -81,9 +90,13 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc // Safari respectively. if (xhr && xhr.readyState == 4) { var responseHeaders = null, - response = null; + response = null, + statusText = ''; - if(status !== ABORTED) { + if (status === TIMEOUT) { + statusText = 'Timeout'; + } else if(status !== ABORTED) { + statusText = xhr.statusText || ''; responseHeaders = xhr.getAllResponseHeaders(); // responseText is the old-school way of retrieving response (supported by IE8 & 9) @@ -95,7 +108,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc status || xhr.status, response, responseHeaders, - xhr.statusText || ''); + statusText); } }; @@ -131,7 +144,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc function timeoutRequest() { - status = ABORTED; + status = TIMEOUT; jsonpDone && jsonpDone(); xhr && xhr.abort(); } diff --git a/test/ng/httpBackendSpec.js b/test/ng/httpBackendSpec.js index 9aed1578f395..6fde5913e0e7 100644 --- a/test/ng/httpBackendSpec.js +++ b/test/ng/httpBackendSpec.js @@ -164,7 +164,7 @@ describe('$httpBackend', function() { it('should not try to read response data when request is aborted', function() { callback.andCallFake(function(status, response, headers) { - expect(status).toBe(-1); + expect(status).toBe(-2); expect(response).toBe(null); expect(headers).toBe(null); }); @@ -183,7 +183,7 @@ describe('$httpBackend', function() { it('should abort request on timeout', function() { callback.andCallFake(function(status, response) { - expect(status).toBe(-1); + expect(status).toBe(-2); }); $backend('GET', '/url', null, callback, {}, 2000); @@ -204,7 +204,7 @@ describe('$httpBackend', function() { it('should abort request on timeout promise resolution', inject(function($timeout) { callback.andCallFake(function(status, response) { - expect(status).toBe(-1); + expect(status).toBe(-2); }); $backend('GET', '/url', null, callback, {}, $timeout(noop, 2000)); diff --git a/test/ng/httpSpec.js b/test/ng/httpSpec.js index 27017061635c..1318bbc16e63 100644 --- a/test/ng/httpSpec.js +++ b/test/ng/httpSpec.js @@ -1308,6 +1308,20 @@ describe('$http', function() { $httpBackend.verifyNoOutstandingExpectation(); $httpBackend.verifyNoOutstandingRequest(); })); + + + it('should reject promise when timeout promise resolves', inject(function($timeout) { + var onFulfilled = jasmine.createSpy('onFulfilled'); + var onRejected = jasmine.createSpy('onRejected'); + $httpBackend.expect('GET', '/some').respond(200); + + $http({method: 'GET', url: '/some', timeout: $timeout(noop, 10)}).then(onFulfilled, onRejected); + + $timeout.flush(100); + + expect(onFulfilled).not.toHaveBeenCalled(); + expect(onRejected).toHaveBeenCalledOnce(); + })); });