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

Commit 549edc9

Browse files
committed
fix($httpBackend): complete the request on timeout
When using the [timeout attribute](https://xhr.spec.whatwg.org/#the-timeout-attribute) and an XHR request times out, browsers trigger the `timeout` event (and execute the XHR's `ontimeout` callback). Additionally, Safari 9 handles timed-out requests in the same way, even if no `timeout` has been explicitly set on the XHR. In the above cases, `$httpBackend` would fail to capture the XHR's completing (with an error), so the corresponding `$http` promise would never get fulfilled. Note that using `$http`'s `timeout` configuration option does **not** rely on the XHR's `timeout` property (or its `ontimeout` callback). Fixes #14969 Closes #14972
1 parent 6ed4c61 commit 549edc9

File tree

2 files changed

+25
-14
lines changed

2 files changed

+25
-14
lines changed

src/ng/httpBackend.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
111111

112112
xhr.onerror = requestError;
113113
xhr.onabort = requestError;
114+
xhr.ontimeout = requestError;
114115

115116
forEach(eventHandlers, function(value, key) {
116117
xhr.addEventListener(key, value);

test/ng/httpBackendSpec.js

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -155,10 +155,11 @@ describe('$httpBackend', function() {
155155
});
156156

157157
it('should not try to read response data when request is aborted', function() {
158-
callback.and.callFake(function(status, response, headers) {
158+
callback.and.callFake(function(status, response, headers, statusText) {
159159
expect(status).toBe(-1);
160160
expect(response).toBe(null);
161161
expect(headers).toBe(null);
162+
expect(statusText).toBe('');
162163
});
163164
$backend('GET', '/url', null, callback, {}, 2000);
164165
xhr = MockXhr.$$lastInstance;
@@ -172,6 +173,22 @@ describe('$httpBackend', function() {
172173
expect(callback).toHaveBeenCalledOnce();
173174
});
174175

176+
it('should complete the request on timeout', function() {
177+
callback.and.callFake(function(status, response, headers, statusText) {
178+
expect(status).toBe(-1);
179+
expect(response).toBe(null);
180+
expect(headers).toBe(null);
181+
expect(statusText).toBe('');
182+
});
183+
$backend('GET', '/url', null, callback, {});
184+
xhr = MockXhr.$$lastInstance;
185+
186+
expect(callback).not.toHaveBeenCalled();
187+
188+
xhr.ontimeout();
189+
expect(callback).toHaveBeenCalledOnce();
190+
});
191+
175192
it('should abort request on timeout', function() {
176193
callback.and.callFake(function(status, response) {
177194
expect(status).toBe(-1);
@@ -253,6 +270,7 @@ describe('$httpBackend', function() {
253270
expect(MockXhr.$$lastInstance.withCredentials).toBe(true);
254271
});
255272

273+
256274
it('should call $xhrFactory with method and url', function() {
257275
var mockXhrFactory = jasmine.createSpy('mockXhrFactory').and.callFake(createMockXhr);
258276
$backend = createHttpBackend($browser, mockXhrFactory, $browser.defer, $jsonpCallbacks, fakeDocument);
@@ -391,6 +409,7 @@ describe('$httpBackend', function() {
391409
// TODO(vojta): test whether it fires "async-end" on both success and error
392410
});
393411

412+
394413
describe('protocols that return 0 status code', function() {
395414

396415
function respond(status, content) {
@@ -400,10 +419,12 @@ describe('$httpBackend', function() {
400419
xhr.onload();
401420
}
402421

403-
404-
it('should convert 0 to 200 if content and file protocol', function() {
422+
beforeEach(function() {
405423
$backend = createHttpBackend($browser, createMockXhr);
424+
});
425+
406426

427+
it('should convert 0 to 200 if content and file protocol', function() {
407428
$backend('GET', 'file:///whatever/index.html', null, callback);
408429
respond(0, 'SOME CONTENT');
409430

@@ -412,8 +433,6 @@ describe('$httpBackend', function() {
412433
});
413434

414435
it('should convert 0 to 200 if content for protocols other than file', function() {
415-
$backend = createHttpBackend($browser, createMockXhr);
416-
417436
$backend('GET', 'someProtocol:///whatever/index.html', null, callback);
418437
respond(0, 'SOME CONTENT');
419438

@@ -422,8 +441,6 @@ describe('$httpBackend', function() {
422441
});
423442

424443
it('should convert 0 to 404 if no content and file protocol', function() {
425-
$backend = createHttpBackend($browser, createMockXhr);
426-
427444
$backend('GET', 'file:///whatever/index.html', null, callback);
428445
respond(0, '');
429446

@@ -432,8 +449,6 @@ describe('$httpBackend', function() {
432449
});
433450

434451
it('should not convert 0 to 404 if no content for protocols other than file', function() {
435-
$backend = createHttpBackend($browser, createMockXhr);
436-
437452
$backend('GET', 'someProtocol:///whatever/index.html', null, callback);
438453
respond(0, '');
439454

@@ -460,8 +475,6 @@ describe('$httpBackend', function() {
460475

461476
try {
462477

463-
$backend = createHttpBackend($browser, createMockXhr);
464-
465478
$backend('GET', '/whatever/index.html', null, callback);
466479
respond(0, '');
467480

@@ -473,10 +486,7 @@ describe('$httpBackend', function() {
473486
}
474487
});
475488

476-
477489
it('should return original backend status code if different from 0', function() {
478-
$backend = createHttpBackend($browser, createMockXhr);
479-
480490
// request to http://
481491
$backend('POST', 'http://rest_api/create_whatever', null, callback);
482492
respond(201, '');

0 commit comments

Comments
 (0)