refactor(http): use Observables in Http backends

BREAKING CHANGE: Http now returns Rx Observables directly, so calling .toRx() is no longer necessary. Additionally, Http calls are now cold, so backend requests will not fire unless .subscribe() is called.

closes #4043 and closes #2974

Closes #4376
This commit is contained in:
Rob Wormald
2015-09-25 18:53:32 -04:00
parent 3dd9919bbd
commit a88e6f3106
8 changed files with 163 additions and 166 deletions

View File

@ -38,11 +38,16 @@ class MockBrowserJsonp extends BrowserJsonp {
addEventListener(type: string, cb: (data: any) => any) { this.callbacks.set(type, cb); }
removeEventListener(type: string, cb: Function) { this.callbacks.delete(type); }
dispatchEvent(type: string, argument?: any) {
if (!isPresent(argument)) {
argument = {};
}
this.callbacks.get(type)(argument);
let cb = this.callbacks.get(type);
if (isPresent(cb)) {
cb(argument);
}
}
build(url: string) {
@ -89,7 +94,7 @@ export function main() {
inject([AsyncTestCompleter], async => {
let connection = new JSONPConnection(sampleRequest, new MockBrowserJsonp(),
new ResponseOptions({type: ResponseTypes.Error}));
ObservableWrapper.subscribe<Response>(connection.response, res => {
connection.response.subscribe(res => {
expect(res.type).toBe(ResponseTypes.Error);
async.done();
});
@ -104,17 +109,17 @@ export function main() {
let errorSpy = spy.spy('error');
let returnSpy = spy.spy('cancelled');
ObservableWrapper.subscribe(connection.response, loadSpy, errorSpy, returnSpy);
connection.dispose();
expect(connection.readyState).toBe(ReadyStates.Cancelled);
let request = connection.response.subscribe(loadSpy, errorSpy, returnSpy);
request.unsubscribe();
connection.finished('Fake data');
existingScripts[0].dispatchEvent('load');
TimerWrapper.setTimeout(() => {
expect(connection.readyState).toBe(ReadyStates.Cancelled);
expect(loadSpy).not.toHaveBeenCalled();
expect(errorSpy).not.toHaveBeenCalled();
expect(returnSpy).toHaveBeenCalled();
expect(returnSpy).not.toHaveBeenCalled();
async.done();
}, 10);
}));
@ -122,8 +127,7 @@ export function main() {
it('should report error if loaded without invoking callback',
inject([AsyncTestCompleter], async => {
let connection = new JSONPConnection(sampleRequest, new MockBrowserJsonp());
ObservableWrapper.subscribe(
connection.response,
connection.response.subscribe(
res => {
expect("response listener called").toBe(false);
async.done();
@ -139,15 +143,15 @@ export function main() {
it('should report error if script contains error', inject([AsyncTestCompleter], async => {
let connection = new JSONPConnection(sampleRequest, new MockBrowserJsonp());
ObservableWrapper.subscribe(connection.response,
res => {
expect("response listener called").toBe(false);
async.done();
},
err => {
expect(err['message']).toBe('Oops!');
async.done();
});
connection.response.subscribe(
res => {
expect("response listener called").toBe(false);
async.done();
},
err => {
expect(err['message']).toBe('Oops!');
async.done();
});
existingScripts[0].dispatchEvent('error', ({message: "Oops!"}));
}));
@ -159,14 +163,15 @@ export function main() {
let base = new BaseRequestOptions();
let req = new Request(
base.merge(new RequestOptions({url: 'https://google.com', method: method})));
expect(() => new JSONPConnection(req, new MockBrowserJsonp())).toThrowError();
expect(() => new JSONPConnection(req, new MockBrowserJsonp()).response.subscribe())
.toThrowError();
});
});
it('should respond with data passed to callback', inject([AsyncTestCompleter], async => {
let connection = new JSONPConnection(sampleRequest, new MockBrowserJsonp());
ObservableWrapper.subscribe<Response>(connection.response, res => {
connection.response.subscribe(res => {
expect(res.json()).toEqual(({fake_payload: true, blob_id: 12345}));
async.done();
});

View File

@ -58,6 +58,8 @@ class MockBrowserXHR extends BrowserXhr {
addEventListener(type: string, cb: Function) { this.callbacks.set(type, cb); }
removeEventListener(type: string, cb: Function) { this.callbacks.delete(type); }
dispatchEvent(type: string) { this.callbacks.get(type)({}); }
build() {
@ -95,7 +97,7 @@ export function main() {
inject([AsyncTestCompleter], async => {
var connection = new XHRConnection(sampleRequest, new MockBrowserXHR(),
new ResponseOptions({type: ResponseTypes.Error}));
ObservableWrapper.subscribe<Response>(connection.response, res => {
connection.response.subscribe(res => {
expect(res.type).toBe(ResponseTypes.Error);
async.done();
});
@ -105,40 +107,44 @@ export function main() {
it('should complete a request', inject([AsyncTestCompleter], async => {
var connection = new XHRConnection(sampleRequest, new MockBrowserXHR(),
new ResponseOptions({type: ResponseTypes.Error}));
ObservableWrapper.subscribe<Response>(connection.response, res => {
expect(res.type).toBe(ResponseTypes.Error);
}, null, () => { async.done(); });
connection.response.subscribe(res => { expect(res.type).toBe(ResponseTypes.Error); },
null, () => { async.done(); });
existingXHRs[0].dispatchEvent('load');
}));
it('should call abort when disposed', () => {
var connection = new XHRConnection(sampleRequest, new MockBrowserXHR());
connection.dispose();
var request = connection.response.subscribe();
request.unsubscribe();
expect(abortSpy).toHaveBeenCalled();
});
it('should create an error Response on error', inject([AsyncTestCompleter], async => {
var connection = new XHRConnection(sampleRequest, new MockBrowserXHR(),
new ResponseOptions({type: ResponseTypes.Error}));
ObservableWrapper.subscribe(connection.response, null, res => {
connection.response.subscribe(null, res => {
expect(res.type).toBe(ResponseTypes.Error);
async.done();
});
existingXHRs[0].dispatchEvent('error');
}));
it('should automatically call open with method and url', () => {
new XHRConnection(sampleRequest, new MockBrowserXHR());
it('should call open with method and url when subscribed to', () => {
var connection = new XHRConnection(sampleRequest, new MockBrowserXHR());
expect(openSpy).not.toHaveBeenCalled();
connection.response.subscribe();
expect(openSpy).toHaveBeenCalledWith('GET', sampleRequest.url);
});
it('should automatically call send on the backend with request body', () => {
it('should call send on the backend with request body when subscribed to', () => {
var body = 'Some body to love';
var base = new BaseRequestOptions();
new XHRConnection(new Request(base.merge(new RequestOptions({body: body}))),
new MockBrowserXHR());
var connection = new XHRConnection(
new Request(base.merge(new RequestOptions({body: body}))), new MockBrowserXHR());
expect(sendSpy).not.toHaveBeenCalled();
connection.response.subscribe();
expect(sendSpy).toHaveBeenCalledWith(body);
});
@ -146,8 +152,9 @@ export function main() {
var headers = new Headers({'Content-Type': 'text/xml', 'Breaking-Bad': '<3'});
var base = new BaseRequestOptions();
new XHRConnection(new Request(base.merge(new RequestOptions({headers: headers}))),
new MockBrowserXHR());
var connection = new XHRConnection(
new Request(base.merge(new RequestOptions({headers: headers}))), new MockBrowserXHR());
connection.response.subscribe();
expect(setRequestHeaderSpy).toHaveBeenCalledWith('Content-Type', ['text/xml']);
expect(setRequestHeaderSpy).toHaveBeenCalledWith('Breaking-Bad', ['<3']);
});
@ -157,7 +164,7 @@ export function main() {
var connection = new XHRConnection(sampleRequest, new MockBrowserXHR(),
new ResponseOptions({status: statusCode}));
ObservableWrapper.subscribe<Response>(connection.response, res => {
connection.response.subscribe(res => {
expect(res.status).toBe(statusCode);
async.done();
});
@ -172,7 +179,7 @@ export function main() {
var connection = new XHRConnection(sampleRequest, new MockBrowserXHR(),
new ResponseOptions({status: statusCode}));
ObservableWrapper.subscribe<Response>(connection.response, res => {
connection.response.subscribe(res => {
expect(res.status).toBe(normalizedCode);
async.done();
});
@ -190,19 +197,18 @@ export function main() {
var connection2 =
new XHRConnection(sampleRequest, new MockBrowserXHR(), new ResponseOptions());
ObservableWrapper.subscribe<Response>(connection1.response, res => {
connection1.response.subscribe(res => {
expect(res.text()).toBe(responseBody);
ObservableWrapper.subscribe<Response>(connection2.response, ress => {
connection2.response.subscribe(ress => {
expect(ress.text()).toBe(responseBody);
async.done();
});
existingXHRs[1].setResponse(responseBody);
existingXHRs[1].dispatchEvent('load');
});
existingXHRs[0].setResponseText(responseBody);
existingXHRs[1].setResponse(responseBody);
existingXHRs[0].dispatchEvent('load');
}));