aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Bennett2013-04-24 12:33:08 -0500
committerPete Bacon Darwin2013-04-30 20:42:34 +0100
commitcda7b71146f6748116ad5bbc9050ee7e79a9ce2b (patch)
treeb0a77296446f6f2bb7005c8ec26bf1059c64ca7e
parentfc25a443f8595ec8761675595af58ec873452896 (diff)
downloadangular.js-cda7b71146f6748116ad5bbc9050ee7e79a9ce2b.tar.bz2
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.
-rw-r--r--src/ng/httpBackend.js24
-rw-r--r--test/ng/httpBackendSpec.js53
2 files changed, 66 insertions, 11 deletions
diff --git a/src/ng/httpBackend.js b/src/ng/httpBackend.js
index a3f6cdc0..69ac5976 100644
--- a/src/ng/httpBackend.js
+++ b/src/ng/httpBackend.js
@@ -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;
}
}
diff --git a/test/ng/httpBackendSpec.js b/test/ng/httpBackendSpec.js
index a7935a7c..da4fed16 100644
--- a/test/ng/httpBackendSpec.js
+++ b/test/ng/httpBackendSpec.js
@@ -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
});