Completing pending calls when the connection is closed

Also fixing a small leak where a callback would not be removed if inovke failed due to a send error
This commit is contained in:
moozzyk 2017-03-21 10:56:49 -07:00
parent 09667e86ab
commit af22fec225
2 changed files with 101 additions and 5 deletions

View File

@ -0,0 +1,75 @@
import { IConnection } from "../Microsoft.AspNetCore.SignalR.Client.TS/IConnection"
import { HubConnection } from "../Microsoft.AspNetCore.SignalR.Client.TS/HubConnection"
import { DataReceived, ConnectionClosed } from "../Microsoft.AspNetCore.SignalR.Client.TS/Common"
describe("HubConnection", () => {
it("completes pending invocations when stopped", async (done) => {
let connection: IConnection = {
start(transportName: string): Promise<void> {
return Promise.resolve();
},
send(data: any): Promise<void> {
return Promise.resolve();
},
stop(): void {
if (this.onClosed) {
this.onClosed();
}
},
onDataReceived: null,
onClosed: null
};
let hubConnection = new HubConnection(connection);
var invokePromise = hubConnection.invoke("testMethod");
hubConnection.stop();
invokePromise
.then(() => {
fail();
done();
})
.catch((error: Error) => {
expect(error.message).toBe("Invocation cancelled due to connection being closed.");
done();
});
});
it("completes pending invocations when connection is lost", async (done) => {
let connection: IConnection = {
start(transportName: string): Promise<void> {
return Promise.resolve();
},
send(data: any): Promise<void> {
return Promise.resolve();
},
stop(): void {
if (this.onClosed) {
this.onClosed();
}
},
onDataReceived: null,
onClosed: null
};
let hubConnection = new HubConnection(connection);
var invokePromise = hubConnection.invoke("testMethod");
invokePromise
.then(() => {
fail();
done();
})
.catch((error: Error) => {
expect(error.message).toBe("Connection lost");
done();
});
// Typically this would be called by the transport
connection.onClosed(new Error("Connection lost"));
});
});

View File

@ -21,6 +21,7 @@ export class HubConnection {
private callbacks: Map<string, (invocationDescriptor: InvocationResultDescriptor) => void>;
private methods: Map<string, (...args: any[]) => void>;
private id: number;
private connectionClosedCallback: ConnectionClosed;
static create(url: string, queryString?: string): HubConnection {
return new this(new Connection(url, queryString))
@ -33,6 +34,9 @@ export class HubConnection {
this.connection.onDataReceived = data => {
this.onDataReceived(data);
};
this.connection.onClosed = (error: Error) => {
this.onConnectionClosed(error);
}
this.callbacks = new Map<string, (invocationDescriptor: InvocationResultDescriptor) => void>();
this.methods = new Map<string, (...args: any[]) => void>();
@ -48,7 +52,7 @@ export class HubConnection {
var descriptor = JSON.parse(data);
if (descriptor.Method === undefined) {
let invocationResult: InvocationResultDescriptor = descriptor;
let callback = this.callbacks[invocationResult.Id];
let callback = this.callbacks.get(invocationResult.Id);
if (callback != null) {
callback(invocationResult);
this.callbacks.delete(invocationResult.Id);
@ -64,6 +68,23 @@ export class HubConnection {
}
}
private onConnectionClosed(error: Error) {
let errorInvocationResult = {
Id: "-1",
Error: error ? error.message : "Invocation cancelled due to connection being closed.",
Result: null
} as InvocationResultDescriptor;
this.callbacks.forEach(callback => {
callback(errorInvocationResult);
});
this.callbacks.clear();
if (this.connectionClosedCallback) {
this.connectionClosedCallback(error);
}
}
start(transportName? :string): Promise<void> {
return this.connection.start(transportName);
}
@ -83,20 +104,20 @@ export class HubConnection {
};
let p = new Promise<any>((resolve, reject) => {
this.callbacks[id] = (invocationResult: InvocationResultDescriptor) => {
this.callbacks.set(invocationDescriptor.Id, (invocationResult: InvocationResultDescriptor) => {
if (invocationResult.Error != null) {
reject(new Error(invocationResult.Error));
}
else {
resolve(invocationResult.Result);
}
};
});
//TODO: separate conversion to enable different data formats
this.connection.send(JSON.stringify(invocationDescriptor))
.catch(e => {
// TODO: remove callback
reject(e);
this.callbacks.delete(invocationDescriptor.Id);
});
});
@ -108,6 +129,6 @@ export class HubConnection {
}
set onClosed(callback: ConnectionClosed) {
this.connection.onClosed = callback;
this.connectionClosedCallback = callback;
}
}