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

feat($httpBackend): add timeout support for JSONP requests #2503

Closed
wants to merge 1 commit 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
24 changes: 14 additions & 10 deletions src/ng/httpBackend.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ function $HttpBackendProvider() {
function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument, locationProtocol) {
// TODO(vojta): fix the signature
return function(method, url, post, callback, headers, timeout, withCredentials, responseType) {
var status;
$browser.$$incOutstandingRequestCount();
url = url || $browser.url();

Expand All @@ -42,12 +43,12 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
callbacks[callbackId].data = data;
};

jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId),
var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId),
function() {
if (callbacks[callbackId].data) {
completeRequest(callback, 200, callbacks[callbackId].data);
} else {
completeRequest(callback, -2);
completeRequest(callback, status || -2);
}
delete callbacks[callbackId];
});
Expand All @@ -58,8 +59,6 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
if (value) xhr.setRequestHeader(key, value);
});

var status;

// 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
Expand Down Expand Up @@ -105,20 +104,24 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
}

xhr.send(post || '');
}

if (timeout > 0) {
$browserDefer(function() {
status = -1;
xhr.abort();
}, timeout);
}
if (timeout > 0) {
var timeoutId = $browserDefer(function() {
status = -1;
jsonpDone && jsonpDone();
xhr && xhr.abort();
}, timeout);
}


function completeRequest(callback, status, response, headersString) {
// URL_MATCH is defined in src/service/location.js
var protocol = (url.match(SERVER_MATCH) || ['', locationProtocol])[1];

// cancel timeout
timeoutId && $browserDefer.cancel(timeoutId);

// fix status code for file protocol (it's always 0)
status = (protocol == 'file') ? (response ? 200 : 404) : status;

Expand Down Expand Up @@ -152,5 +155,6 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
}

rawDocument.body.appendChild(script);
return doneWrapper;
}
}
53 changes: 52 additions & 1 deletion test/ng/httpBackendSpec.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,36 @@
describe('$httpBackend', function() {

var $backend, $browser, callbacks,
xhr, fakeDocument, callback;
xhr, fakeDocument, callback,
fakeTimeoutId = 0;

// TODO(vojta): should be replaced by $defer mock
function fakeTimeout(fn, delay) {
fakeTimeout.fns.push(fn);
fakeTimeout.delays.push(delay);
fakeTimeout.ids.push(++fakeTimeoutId);
return fakeTimeoutId;
}

fakeTimeout.fns = [];
fakeTimeout.delays = [];
fakeTimeout.ids = [];
fakeTimeout.flush = function() {
var len = fakeTimeout.fns.length;
fakeTimeout.delays = [];
fakeTimeout.ids = [];
while (len--) fakeTimeout.fns.shift()();
};
fakeTimeout.cancel = function(id) {
var i = indexOf(fakeTimeout.ids, id);
if (i >= 0) {
fakeTimeout.fns.splice(i, 1);
fakeTimeout.delays.splice(i, 1);
fakeTimeout.ids.splice(i, 1);
return true;
}
return false;
};


beforeEach(inject(function($injector) {
Expand Down Expand Up @@ -102,6 +117,27 @@ describe('$httpBackend', function() {
});


it('should cancel timeout on completion', function() {
callback.andCallFake(function(status, response) {
expect(status).toBe(200);
});

$backend('GET', '/url', null, callback, {}, 2000);
xhr = MockXhr.$$lastInstance;
spyOn(xhr, 'abort');

expect(fakeTimeout.delays[0]).toBe(2000);

xhr.status = 200;
xhr.readyState = 4;
xhr.onreadystatechange();
expect(callback).toHaveBeenCalledOnce();

expect(fakeTimeout.delays.length).toBe(0);
expect(xhr.abort).not.toHaveBeenCalled();
});


it('should register onreadystatechange callback before sending', function() {
// send() in IE6, IE7 is sync when serving from cache
function SyncXhr() {
Expand Down Expand Up @@ -239,6 +275,21 @@ describe('$httpBackend', function() {
});


it('should abort request on timeout', function() {
callback.andCallFake(function(status, response) {
expect(status).toBe(-1);
});

$backend('JSONP', 'http://example.org/path?cb=JSON_CALLBACK', null, callback, null, 2000);
expect(fakeDocument.$$scripts.length).toBe(1);
expect(fakeTimeout.delays[0]).toBe(2000);

fakeTimeout.flush();
expect(fakeDocument.$$scripts.length).toBe(0);
expect(callback).toHaveBeenCalledOnce();
});


// TODO(vojta): test whether it fires "async-start"
// TODO(vojta): test whether it fires "async-end" on both success and error
});
Expand Down