diff --git a/src/ng/http.js b/src/ng/http.js index deeb6cbb2621..03c720b23c5d 100644 --- a/src/ng/http.js +++ b/src/ng/http.js @@ -162,7 +162,7 @@ function $HttpProvider() { * # General usage * The `$http` service is a function which takes a single argument — a configuration object — * that is used to generate an http request and returns a {@link ng.$q promise} - * with two $http specific methods: `success` and `error`. + * with three $http specific methods: `success`, `error`, and `abort`. * *
* $http({method: 'GET', url: '/someUrl'}).
@@ -383,12 +383,13 @@ function $HttpProvider() {
* requests with credentials} for more information.
*
* @returns {HttpPromise} Returns a {@link ng.$q promise} object with the
- * standard `then` method and two http specific methods: `success` and `error`. The `then`
- * method takes two arguments a success and an error callback which will be called with a
- * response object. The `success` and `error` methods take a single argument - a function that
- * will be called when the request succeeds or fails respectively. The arguments passed into
- * these functions are destructured representation of the response object passed into the
- * `then` method. The response object has these properties:
+ * standard `then` method and three http specific methods: `success`, `error`, and `abort`.
+ * The `then` method takes two arguments a success and an error callback which will be called
+ * with a response object. The `abort` method will cancel a pending request, causing it to
+ * fail. The `success` and `error` methods take a single argument - a function that will be
+ * called when the request succeeds or fails respectively. The arguments passed into these
+ * functions are destructured representation of the response object passed into the `then`
+ * method. The response object has these properties:
*
* - **data** – `{string|Object}` – The response body transformed with the transform functions.
* - **status** – `{number}` – HTTP status code of the response.
@@ -479,7 +480,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 +490,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 +666,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 +674,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 +708,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..eb46601f0a45 100644
--- a/src/ng/httpBackend.js
+++ b/src/ng/httpBackend.js
@@ -52,7 +52,12 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
delete callbacks[callbackId];
});
} else {
- var xhr = new XHR();
+ var xhr = new XHR(),
+ abortRequest = function() {
+ status = -1;
+ xhr.abort();
+ };
+
xhr.open(method, url, true);
forEach(headers, function(value, key) {
if (value) xhr.setRequestHeader(key, value);
@@ -77,11 +82,10 @@ 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;
}
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;
+ });
});