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

Add abort method to $http promise. #1623

Closed
wants to merge 4 commits into from
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
30 changes: 21 additions & 9 deletions src/ng/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
*
* <pre>
* $http({method: 'GET', url: '/someUrl'}).
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)) {
Expand All @@ -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) {
Expand Down Expand Up @@ -661,13 +666,20 @@ function $HttpProvider() {
function sendReq(config, reqData, reqHeaders) {
var deferred = $q.defer(),
promise = deferred.promise,
abortFn,
cache,
cachedResp,
url = buildUrl(config.url, config.params);

$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;
Expand Down Expand Up @@ -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);
}

Expand Down
14 changes: 9 additions & 5 deletions src/ng/httpBackend.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
}


Expand Down
21 changes: 21 additions & 0 deletions test/ng/httpBackendSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
32 changes: 32 additions & 0 deletions test/ng/httpSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
});
});