From fb16ce93d83924267953eaa7560119e02f746adc Mon Sep 17 00:00:00 2001 From: David Bennett Date: Wed, 28 Nov 2012 18:50:27 -0600 Subject: [PATCH] Added new feature to abort a pending request. Fixes issue #1159. --- src/ng/http.js | 15 +++++++++++++-- src/ng/httpBackend.js | 12 ++++++++---- test/ng/httpBackendSpec.js | 21 +++++++++++++++++++++ test/ng/httpSpec.js | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 6 deletions(-) diff --git a/src/ng/http.js b/src/ng/http.js index deeb6cbb2621..6eae0f3de3e2 100644 --- a/src/ng/http.js +++ b/src/ng/http.js @@ -479,7 +479,7 @@ function $HttpProvider() { reqHeaders = extend({'X-XSRF-TOKEN': $browser.cookies()['XSRF-TOKEN']}, defHeaders.common, defHeaders[lowercase(config.method)], config.headers), reqData = transformData(config.data, headersGetter(reqHeaders), reqTransformFn), - promise; + promise, abortFn; // strip content-type if data is undefined if (isUndefined(config.data)) { @@ -489,13 +489,17 @@ function $HttpProvider() { // send request promise = sendReq(config, reqData, reqHeaders); + // save a reference to the abort function + abortFn = promise.abort; // transform future response promise = promise.then(transformResponse, transformResponse); + promise.abort = abortFn; // apply interceptors forEach(responseInterceptors, function(interceptor) { promise = interceptor(promise); + promise.abort = abortFn; }); promise.success = function(fn) { @@ -661,6 +665,7 @@ function $HttpProvider() { function sendReq(config, reqData, reqHeaders) { var deferred = $q.defer(), promise = deferred.promise, + abortFn, cache, cachedResp, url = buildUrl(config.url, config.params); @@ -668,6 +673,12 @@ function $HttpProvider() { $http.pendingRequests.push(config); promise.then(removePendingReq, removePendingReq); + promise.abort = function() { + if (isFunction(abortFn)) { + abortFn(); + } + } + if (config.cache && config.method == 'GET') { cache = isObject(config.cache) ? config.cache : defaultCache; @@ -696,7 +707,7 @@ function $HttpProvider() { // if we won't have the response in cache, send the request to the backend if (!cachedResp) { - $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, + abortFn = $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, config.withCredentials); } diff --git a/src/ng/httpBackend.js b/src/ng/httpBackend.js index 0a12aa23b4a5..856c0c9988c0 100644 --- a/src/ng/httpBackend.js +++ b/src/ng/httpBackend.js @@ -77,11 +77,15 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument, xhr.send(post || ''); if (timeout > 0) { - $browserDefer(function() { - status = -1; - xhr.abort(); - }, timeout); + $browserDefer(abortRequest, timeout); } + + return abortRequest; + + function abortRequest() { + status = -1; + xhr.abort(); + }; } diff --git a/test/ng/httpBackendSpec.js b/test/ng/httpBackendSpec.js index 06b63c3c8c8d..39d7f4a4fd4f 100644 --- a/test/ng/httpBackendSpec.js +++ b/test/ng/httpBackendSpec.js @@ -81,6 +81,27 @@ describe('$httpBackend', function() { }); + it('should return an abort function', function() { + callback.andCallFake(function(status, response) { + expect(status).toBe(-1); + }); + + var abort = $backend('GET', '/url', null, callback); + xhr = MockXhr.$$lastInstance; + spyOn(xhr, 'abort'); + + expect(typeof abort).toBe('function'); + + abort(); + expect(xhr.abort).toHaveBeenCalledOnce(); + + xhr.status = 0; + xhr.readyState = 4; + xhr.onreadystatechange(); + expect(callback).toHaveBeenCalledOnce(); + }); + + it('should abort request on timeout', function() { callback.andCallFake(function(status, response) { expect(status).toBe(-1); diff --git a/test/ng/httpSpec.js b/test/ng/httpSpec.js index bb4de3c1a5e2..0e8dbb1e38d1 100644 --- a/test/ng/httpSpec.js +++ b/test/ng/httpSpec.js @@ -978,4 +978,36 @@ describe('$http', function() { $httpBackend.verifyNoOutstandingExpectation = noop; }); + + + it('should abort pending requests', function() { + var $httpBackend = jasmine.createSpy('$httpBackend'); + var abortFn = jasmine.createSpy('abortFn'); + + $httpBackend.andCallFake(function(m, u, d, callback) { + abortFn.andCallFake(function() { + callback(-1, 'bad error', ''); + }); + return abortFn; + }); + + module(function($provide) { + $provide.value('$httpBackend', $httpBackend, ''); + }); + + inject(function($http) { + $http({method: 'GET', url: 'some.html'}).error(function(data, status, headers, config) { + expect(data).toBe('bad error'); + expect(status).toBe(0); + expect(headers()).toEqual({}); + expect(config.url).toBe('some.html'); + callback(); + }).abort(); + expect($httpBackend).toHaveBeenCalledOnce(); + expect(abortFn).toHaveBeenCalledOnce(); + expect(callback).toHaveBeenCalledOnce(); + }); + + $httpBackend.verifyNoOutstandingExpectation = noop; + }); });