Await promise in event handler
RonnyWinkler opened this issue · comments
First a question:
In my case, the app tries to reconnect to HomeAssistant after a "heartbeat" check registeres that the connection is not possible. After the old connection is closed/cleared and a new connection is established, all "old" subscriptions are unsubscribed and and subscribed again (connection.removeEventListener() and call the unsibscribe promise collected from connection.subscribeEvents() ).
In this case I get several event log like: Received event for unknown subscription 15. Unsubscribing.
Is this the correct way it should be done?
Or should the connection never be cleared and a kind of soft-rerconnect shoule be done?
Or should I count on the auto-reconnect of the module in this case and I shoud not reconnect creating a new connection instance?
Now an issue in event handler haws.cjs
:
constructor(socket, options) {
this._handleMessage = (event) => {
let messageGroup = JSON.parse(event.data);
if (!Array.isArray(messageGroup)) {
messageGroup = [messageGroup];
}
messageGroup.forEach((message) => {
const info = this.commands.get(message.id);
switch (message.type) {
case "event":
if (info) {
info.callback(message.event);
}
else {
console.warn(`Received event for unknown subscription ${message.id}. Unsubscribing.`);
this.sendMessagePromise(unsubscribeEvents(message.id));
}
break;
The event handler only uses sync handler functions, but calls a promise inside.
This promise can be rejected. Then You get such unhandled errors:
Unhandled Rejection at: Promise { <rejected> 3 } reason: 3
Shouldn't the handler be async and try/catch the promise call like this (or similar)?:
const info = this.commands.get(message.id);
switch (message.type) {
case "event":
if (info) {
info.callback(message.event);
}
else {
console.warn(`Received event for unknown subscription ${message.id}. Unsubscribing.`);
try{
this.sendMessagePromise(unsubscribeEvents(message.id));
}
catch(error){
console.warn(`Error unsubscribing unknown subscription ${message.id}.`);
}
}
break;
There is another place where an "await" could prevent unhandled exceptions:
haws.cjs
async subscribeMessage(callback, subscribeMessage, options) {
if (this._queuedMessages) {
await new Promise((resolve, reject) => {
this._queuedMessages.push({ resolve, reject });
});
}
let info;
await new Promise((resolve, reject) => {
// Command ID that will be used
const commandId = this._genCmdId();
// We store unsubscribe on info object. That way we can overwrite it in case
// we get disconnected and we have to subscribe again.
info = {
resolve,
reject,
callback,
subscribe: (options === null || options === void 0 ? void 0 : options.resubscribe) !== false
? () => this.subscribeMessage(callback, subscribeMessage)
: undefined,
unsubscribe: async () => {
// No need to unsubscribe if we're disconnected
if (this.connected) {
// old version without await will cause unhandled exception it it thows an exception (HA restart...)
// await this.sendMessagePromise(unsubscribeEvents(commandId));
// suggestion >>>
try{
await this.sendMessagePromise(unsubscribeEvents(commandId));
}
catch(error){
console.error(`Error unsubscribing event, command ID: ${commandId}.`);
}
// <<<
}
this.commands.delete(commandId);
As per the type, event listeners are not expected to be async, and so we shouldn't accommodate that.
But you are calling an async function/promise this.sendMessagePromise from a sync function.
And that's causing unhandled exceptions if tje promise is rejected later.