diff --git a/spec/observables/dom/ajax-spec.ts b/spec/observables/dom/ajax-spec.ts index 1f13878685..510795ac81 100644 --- a/spec/observables/dom/ajax-spec.ts +++ b/spec/observables/dom/ajax-spec.ts @@ -246,6 +246,34 @@ describe('ajax', () => { expect(complete).to.be.true; }); + it('should fail if fails to parse response', () => { + let error: any; + const obj = { + url: '/flibbertyJibbet', + responseType: 'json', + method: '' + }; + + ajax(obj) + .subscribe((x: any) => { + throw 'should not next'; + }, (err: any) => { + error = err; + }, () => { + throw 'should not complete'; + }); + + MockXMLHttpRequest.mostRecent.respondWith({ + 'status': 207, + 'contentType': '', + 'responseType': '', + 'responseText': 'Wee! I am text, but should be valid JSON!' + }); + + expect(error instanceof SyntaxError).to.be.true; + expect(error.message).to.equal('Unexpected token W in JSON at position 0'); + }); + it('should fail on 404', () => { let error: any; const obj = { @@ -312,6 +340,36 @@ describe('ajax', () => { expect(complete).to.be.true; }); + it('should fail if fails to parse error response', () => { + let error: any; + const obj = { + url: '/flibbertyJibbet', + normalizeError: (e: any, xhr: any, type: any) => { + return xhr.response || xhr.responseText; + }, + responseType: 'json', + method: '' + }; + + ajax(obj).subscribe(x => { + throw 'should not next'; + }, (err: any) => { + error = err; + }, () => { + throw 'should not complete'; + }); + + MockXMLHttpRequest.mostRecent.respondWith({ + 'status': 404, + 'contentType': '', + 'responseType': '', + 'responseText': 'Wee! I am text, but should be valid JSON!' + }); + + expect(error instanceof SyntaxError).to.be.true; + expect(error.message).to.equal('Unexpected token W in JSON at position 0'); + }); + it('should succeed no settings', () => { const expected = JSON.stringify({ foo: 'bar' }); @@ -1104,8 +1162,6 @@ class MockXMLHttpRequest { protected defaultResponseValue() { if (this.async === false) { this.response = this.responseText; - } else { - throw new Error('unhandled type "' + this.responseType + '"'); } } @@ -1121,8 +1177,9 @@ class MockXMLHttpRequest { }; this.status = response.status || 200; this.responseText = response.responseText; + const responseType = response.responseType !== undefined ? response.responseType : this.responseType; if (!('response' in response)) { - switch (this.responseType) { + switch (responseType) { case 'json': this.jsonResponseValue(response); break; diff --git a/src/internal/observable/dom/AjaxObservable.ts b/src/internal/observable/dom/AjaxObservable.ts index 3a151110f8..92fbe54d5b 100644 --- a/src/internal/observable/dom/AjaxObservable.ts +++ b/src/internal/observable/dom/AjaxObservable.ts @@ -221,8 +221,11 @@ export class AjaxSubscriber extends Subscriber { this.done = true; const { xhr, request, destination } = this; const response = new AjaxResponse(e, xhr, request); - - destination.next(response); + if (response.response === errorObject) { + destination.error(errorObject.e); + } else { + destination.next(response); + } } private send(): XMLHttpRequest { @@ -320,8 +323,13 @@ export class AjaxSubscriber extends Subscriber { if (progressSubscriber) { progressSubscriber.error(e); } - subscriber.error(new AjaxTimeoutError(this, request)); //TODO: Make betterer. - } + const ajaxTimeoutError = new AjaxTimeoutError(this, request); //TODO: Make betterer. + if (ajaxTimeoutError.response === errorObject) { + subscriber.error(errorObject.e); + } else { + subscriber.error(ajaxTimeoutError); + } + }; xhr.ontimeout = xhrTimeout; (xhrTimeout).request = request; (xhrTimeout).subscriber = this; @@ -346,7 +354,12 @@ export class AjaxSubscriber extends Subscriber { if (progressSubscriber) { progressSubscriber.error(e); } - subscriber.error(new AjaxError('ajax error', this, request)); + const ajaxError = new AjaxError('ajax error', this, request); + if (ajaxError.response === errorObject) { + subscriber.error(errorObject.e); + } else { + subscriber.error(ajaxError); + } }; xhr.onerror = xhrError; (xhrError).request = request; @@ -388,7 +401,12 @@ export class AjaxSubscriber extends Subscriber { if (progressSubscriber) { progressSubscriber.error(e); } - subscriber.error(new AjaxError('ajax error ' + status, this, request)); + const ajaxError = new AjaxError('ajax error ' + status, this, request); + if (ajaxError.response === errorObject) { + subscriber.error(errorObject.e); + } else { + subscriber.error(ajaxError); + } } } } @@ -474,17 +492,21 @@ export class AjaxError extends Error { } } +function parseJson(xhr: XMLHttpRequest) { + // HACK(benlesh): TypeScript shennanigans + // tslint:disable-next-line:no-any XMLHttpRequest is defined to always have 'response' inferring xhr as never for the else clause. + if ('response' in (xhr as any)) { + //IE does not support json as responseType, parse it internally + return xhr.responseType ? xhr.response : JSON.parse(xhr.response || xhr.responseText || 'null'); + } else { + return JSON.parse((xhr as any).responseText || 'null'); + } +} + function parseXhrResponse(responseType: string, xhr: XMLHttpRequest) { switch (responseType) { case 'json': - // HACK(benlesh): TypeScript shennanigans - // tslint:disable-next-line:no-any XMLHttpRequest is defined to always have 'response' inferring xhr as never for the else clause. - if ('response' in (xhr as any)) { - //IE does not support json as responseType, parse it internally - return xhr.responseType ? xhr.response : JSON.parse(xhr.response || xhr.responseText || 'null'); - } else { - return JSON.parse((xhr as any).responseText || 'null'); - } + return tryCatch(parseJson)(xhr); case 'xml': return xhr.responseXML; case 'text':