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

Commit cda7b71

Browse files
David Bennettpetebacondarwin
David Bennett
authored andcommitted
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.
1 parent fc25a44 commit cda7b71

File tree

2 files changed

+66
-11
lines changed

2 files changed

+66
-11
lines changed

src/ng/httpBackend.js

+14-10
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ function $HttpBackendProvider() {
3333
function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument, locationProtocol) {
3434
// TODO(vojta): fix the signature
3535
return function(method, url, post, callback, headers, timeout, withCredentials, responseType) {
36+
var status;
3637
$browser.$$incOutstandingRequestCount();
3738
url = url || $browser.url();
3839

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

45-
jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId),
46+
var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId),
4647
function() {
4748
if (callbacks[callbackId].data) {
4849
completeRequest(callback, 200, callbacks[callbackId].data);
4950
} else {
50-
completeRequest(callback, -2);
51+
completeRequest(callback, status || -2);
5152
}
5253
delete callbacks[callbackId];
5354
});
@@ -58,8 +59,6 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
5859
if (value) xhr.setRequestHeader(key, value);
5960
});
6061

61-
var status;
62-
6362
// In IE6 and 7, this might be called synchronously when xhr.send below is called and the
6463
// response is in the cache. the promise api will ensure that to the app code the api is
6564
// always async
@@ -105,20 +104,24 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
105104
}
106105

107106
xhr.send(post || '');
107+
}
108108

109-
if (timeout > 0) {
110-
$browserDefer(function() {
111-
status = -1;
112-
xhr.abort();
113-
}, timeout);
114-
}
109+
if (timeout > 0) {
110+
var timeoutId = $browserDefer(function() {
111+
status = -1;
112+
jsonpDone && jsonpDone();
113+
xhr && xhr.abort();
114+
}, timeout);
115115
}
116116

117117

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

122+
// cancel timeout
123+
timeoutId && $browserDefer.cancel(timeoutId);
124+
122125
// fix status code for file protocol (it's always 0)
123126
status = (protocol == 'file') ? (response ? 200 : 404) : status;
124127

@@ -152,5 +155,6 @@ function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument,
152155
}
153156

154157
rawDocument.body.appendChild(script);
158+
return doneWrapper;
155159
}
156160
}

test/ng/httpBackendSpec.js

+52-1
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,36 @@
11
describe('$httpBackend', function() {
22

33
var $backend, $browser, callbacks,
4-
xhr, fakeDocument, callback;
4+
xhr, fakeDocument, callback,
5+
fakeTimeoutId = 0;
56

67
// TODO(vojta): should be replaced by $defer mock
78
function fakeTimeout(fn, delay) {
89
fakeTimeout.fns.push(fn);
910
fakeTimeout.delays.push(delay);
11+
fakeTimeout.ids.push(++fakeTimeoutId);
12+
return fakeTimeoutId;
1013
}
1114

1215
fakeTimeout.fns = [];
1316
fakeTimeout.delays = [];
17+
fakeTimeout.ids = [];
1418
fakeTimeout.flush = function() {
1519
var len = fakeTimeout.fns.length;
1620
fakeTimeout.delays = [];
21+
fakeTimeout.ids = [];
1722
while (len--) fakeTimeout.fns.shift()();
1823
};
24+
fakeTimeout.cancel = function(id) {
25+
var i = indexOf(fakeTimeout.ids, id);
26+
if (i >= 0) {
27+
fakeTimeout.fns.splice(i, 1);
28+
fakeTimeout.delays.splice(i, 1);
29+
fakeTimeout.ids.splice(i, 1);
30+
return true;
31+
}
32+
return false;
33+
};
1934

2035

2136
beforeEach(inject(function($injector) {
@@ -102,6 +117,27 @@ describe('$httpBackend', function() {
102117
});
103118

104119

120+
it('should cancel timeout on completion', function() {
121+
callback.andCallFake(function(status, response) {
122+
expect(status).toBe(200);
123+
});
124+
125+
$backend('GET', '/url', null, callback, {}, 2000);
126+
xhr = MockXhr.$$lastInstance;
127+
spyOn(xhr, 'abort');
128+
129+
expect(fakeTimeout.delays[0]).toBe(2000);
130+
131+
xhr.status = 200;
132+
xhr.readyState = 4;
133+
xhr.onreadystatechange();
134+
expect(callback).toHaveBeenCalledOnce();
135+
136+
expect(fakeTimeout.delays.length).toBe(0);
137+
expect(xhr.abort).not.toHaveBeenCalled();
138+
});
139+
140+
105141
it('should register onreadystatechange callback before sending', function() {
106142
// send() in IE6, IE7 is sync when serving from cache
107143
function SyncXhr() {
@@ -239,6 +275,21 @@ describe('$httpBackend', function() {
239275
});
240276

241277

278+
it('should abort request on timeout', function() {
279+
callback.andCallFake(function(status, response) {
280+
expect(status).toBe(-1);
281+
});
282+
283+
$backend('JSONP', 'http://example.org/path?cb=JSON_CALLBACK', null, callback, null, 2000);
284+
expect(fakeDocument.$$scripts.length).toBe(1);
285+
expect(fakeTimeout.delays[0]).toBe(2000);
286+
287+
fakeTimeout.flush();
288+
expect(fakeDocument.$$scripts.length).toBe(0);
289+
expect(callback).toHaveBeenCalledOnce();
290+
});
291+
292+
242293
// TODO(vojta): test whether it fires "async-start"
243294
// TODO(vojta): test whether it fires "async-end" on both success and error
244295
});

0 commit comments

Comments
 (0)