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

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.
This commit is contained in:
David Bennett 2013-04-24 12:33:08 -05:00 committed by Pete Bacon Darwin
parent fc25a443f8
commit cda7b71146
2 changed files with 66 additions and 11 deletions

View file

@ -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();
@ -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];
});
@ -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
@ -105,13 +104,14 @@ 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);
}
@ -119,6 +119,9 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
// 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;
@ -152,5 +155,6 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
}
rawDocument.body.appendChild(script);
return doneWrapper;
}
}

View file

@ -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) {
@ -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() {
@ -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
});