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

Commit

Permalink
feat($httpBackend): add timeout support for JSONP requests
Browse files Browse the repository at this point in the history
Documentation implies that timeout works for all requests, though it
only works with XHR. To implement:
- Change $httpBackend to set a timeout for JSONP requests which will
immediately resolve the request when fired.
- Cancel the timeout when requests are completed.
  • Loading branch information
David Bennett authored and petebacondarwin committed Apr 30, 2013
1 parent fc25a44 commit cda7b71
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 11 deletions.
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

0 comments on commit cda7b71

Please sign in to comment.