I have a question regarding SignalR on the clientside (JS). Is there a way to intercept incoming messages at the clientside? I am working on a chat application with a lot traffic on the socket. I want to be able to execute code (catch errors if any) on every incoming event.
Looking at the docs I can't find any existing solution for this. Is there one?
After a bit of research, this does not seem to be possible.
My first thought for a workaround was using a function to 'pipe' the callback functions through first.
E.G.
private pipeSignal(cb: (arg: any) => void): (arg: any) => void {
const errors = checkForErrors();
if(errors){
return () => {
// Handle error here
};
}
return cb;
}
I then thought setting up the handlers could look as follows:
this.hubConnection.on(
'MyMethod1',
this.pipeSignal((method1Val) => {
console.log(method1Val);
})
);
this.hubConnection.on(
'MyMethod2',
this.pipeSignal((method2Val) => {
console.log(method2Val);
})
);
BUT, this results in the 'pipeSignal' function being called only once when setting up the event listener. Thereafter, it will not be called.
I think the only option here would be to have a function that checks for errors in every event listener callback.
private hasErrors(arg: any): boolean {
const errors = checkForErrors();
if(errors){
// Handle error here
return true;
}
return false;
}
Then in each signal event callback you can try checking for errors like this:
this.hubConnection.on(
'MyMethod1',
(method1Val) => {
if(!this.hasErrors(method1Val)){
// Can continue knowing there are no errors
console.log(method1Val);
}
}
);
UPDATE
Turns out I was close with my 'pipeSignal' function. Rather than running the error logic in the pipeSignal function, I return a function that wraps the error logic and the callback:
private pipeSignal(cb: (arg: any) => void): (arg: any) => void {
return (arg) => {
const errors = checkForErrors();
if(errors){
// handle errors
} else {
// proceed with callback as there are no errors
cb(arg);
}
};
}
Now this can be used as I imagined before:
this.hubConnection.on(
'MyMethod1',
this.pipeSignal((method1Val) => {
console.log(method1Val);
})
);
Related
I'm working on a Telegram bot. I am trying to create a way to wait for a user response using the promises, but the promise I created to accomplish this hangs and times out.
The code for the Telegram command handler sends a few messages and registers a handler function for the reply event:
export default async function (ctx: BacchusContext): Promise<void> {
if(!ctx.isAdmin){
await ctx.reply('You are not a recognized administrator. You can\'t use this command.');
return;
}
await ctx.reply('Alright, so we\'re adding a party. Cool. When is it?');
const rep = await new Promise((resolve, reject) => {
ctx.onReply( resolve);
});
await ctx.reply(`On Reply: ${rep}`);
}
The registration of the reply event handler is shown below. The function passed to onReply is saved in a session object independent of the telegram context (a static class member):
private static middleware_reply() {
return (ctx: BacchusContext, next) => {
ctx.onReply = (fcn: (rCtx) => void | Promise<void>) => {
ctx.setSessionData<(replyCtx) => void | Promise<void>>('onReply', fcn);
};
ctx.waitForReply = () => {
return new Promise<string>(resolve => {
ctx.setSessionData<(replyCtx) => void | Promise<void>>('onReply', (replyCtx) => {
return resolve(replyCtx.message.text);
});
});
};
return next();
};
}
As soon as the user sends a text message to the bot, the code checks if a onReply handler was defined for the current session, and if it was it will be executed and then removed. The event handler is only supposed to fire once.
this.bot.on('text', (ctx: BacchusContext, next) => {
const handler = ctx.getSessionData<(replyCtx) => Promise<void>>('onReply');
if(handler){
debug('Executing reply function');
handler(ctx);
// Delete handler
ctx.setSessionData<(replyCtx) => Promise<void>>('onReply', null);
}
return next();
});
The code so far makes sense to me, but I keep getting the following error:
ERROR [unhandledRejection] Promise timed out after 90000 milliseconds 14:38:55
at Timeout._onTimeout (node_modules\p-timeout\index.js:39:64)
at listOnTimeout (node:internal/timers:559:17)
at processTimers (node:internal/timers:502:7)
Why does this promise time out?
EDIT:
What's weird about this code is that the following code works fine:
ctx.onReply(async (replyCtx) => {
await ctx.reply(`On Reply: ${replyCtx.message.text}`);
});
So the callback function works fine, but as soon as it is 'promisified' it stops working. Why could that be?
Just wanted to preemptively say that I am familiar with async/await and promises in JavaScript so no need to link me to some MDN pages for that.
I have a function to fetch user details and display it on the UI.
async function someHttpCall() {
throw 'someHttpCall error'
}
async function fetchUserDetails() {
throw 'fetchUserDetails error'
}
function displayUserDetails(userDetails) {
console.log('userDetails:', userDetails)
}
async function fetchUser() {
try {
const user = await someHttpCall()
try {
const details = await fetchUserDetails(user)
returndisplayUserDetails(details)
} catch (fetchUserDetailsError) {
console.log('fetching user error', fetchUserDetailsError)
}
} catch (someHttpCallError) {
console.log('networking error:', someHttpCallError)
}
}
It first makes HTTP call via someHttpCall and if it succeeds then it proceeds to fetchUserDetails and it that succeeds as well then we display the details on Ui via returndisplayUserDetails.
If someHttpCall failed, we will stop and not make fetchUserDetails call. In other words, we want to separate the error handling for someHttpCall and it’s data handling from fetchUserDetails
The function I wrote is with nested try catch blocks which doesn't scale well if the nesting becomes deep and I was trying to rewrite it for better readability using plain then and catch
This was my first atttempt
function fetchUser2() {
someHttpCall()
.then(
(user) => fetchUserDetails(user),
(someHttpCallError) => {
console.log('networking error:', someHttpCallError)
}
)
.then(
(details) => {
displayUserDetails(details)
}, //
(fetchUserDetailsError) => {
console.log('fetching user error', fetchUserDetailsError)
}
)
}
The problem with this is that the second then will run i.e. displayUserDetails even with someHttpCall failing. To avoid this I had to make the previous .catch blocks throw
so this is the updated version
function fetchUser2() {
someHttpCall()
.then(
(user) => fetchUserDetails(user),
(someHttpCallError) => {
console.log('networking error:', someHttpCallError)
throw someHttpCallError
}
)
.then(
(details) => {
displayUserDetails(details)
}, //
(fetchUserDetailsError) => {
console.log('fetching user error', fetchUserDetailsError)
}
)
}
However now the second catch will get called as a result of the throw. So when the someHttpCall failed, after we handled the someHttpCallError error, we would enter this block (fetchUserDetailsError) => { console.log('fetching user error', fetchUserDetailsError) } which is not good since fetchUserDetails never gets called so we shouldn't need to handle fetchUserDetailsError (I know someHttpCallError became fetchUserDetailsError in this case)
I can add some conditional checks in there to distinguish the two errors but it seems less ideal. So I am wondering how I can improve this by using .then and .catch to achieve the same goal here.
I am wondering how I can improve this by using .then and .catch to achieve the same goal here
You don't get to avoid the nesting if you want to replicate the same behaviour:
function fetchUser2() {
return someHttpCall().then(
(user) => {
return fetchUserDetails(user).then(
(details) => {
return displayUserDetails(details)
},
(fetchUserDetailsError) => {
console.log('fetching user error', fetchUserDetailsError)
}
)
},
(someHttpCallError) => {
console.log('networking error:', someHttpCallError)
throw someHttpCallError
}
)
}
(The exact equivalent to try/catch would use .then(…).catch(…) instead of .then(…, …), but you might not actually want that.)
The function I wrote is [nested] which doesn't scale well if the nesting becomes deep and I was trying to rewrite it for better readability […]
For that, I would recommend to combine await with .catch():
async function fetchUser() {
try {
const user = await someHttpCall().catch(someHttpCallError => {
throw new Error('networking error', {cause: someHttpCallError});
});
const details = await fetchUserDetails(user).catch(fetchUserDetailsError => {
throw new Error('fetching user error', {cause: fetchUserDetailsError});
});
return displayUserDetails(details);
} catch (someError) {
console.log(someError.message, someError.cause);
}
}
(The cause option for Error is still quite new, you might need a polyfill for that)
I can add some conditional checks in there to distinguish the two errors but it seems less ideal.
Actually, that sounds like an ideal situation. That means that you don't have to nest any try / catch blocks which could make you code a lot more readable. This is one of the things that async / await is meant to solve.
A solution could be is to create custom errors by extending the Error interface to be able to determine how and where the error occurs.
class CustomError extends Error {
constructor(name, ...args) {
super(...args)
this.name = name
}
}
Throw your errors within the functions that correspond with the error.
async function someHttpCall() {
throw new CustomError('HttpCallError', 'someHttpCall error');
}
async function fetchUserDetails(user) {
throw new CustomError('UserDetailsError', 'fetchUserDetails error')
}
Now you can control your error flow by checking the name property on the error to differentiate your errors.
async function fetchUser() {
try {
const user = await someHttpCall()
const details = await fetchUserDetails(user)
return displayUserDetails(details)
} catch (error) {
switch(error.name) {
case 'HttpCallError':
console.log('Networking error:', error)
break
case 'UserDetailsError':
console.log('Fetching user error', error)
break
}
}
}
I've been inspired by Rust's Result type (which forces you to handle every potential error along the way).
So what I do is handle exceptions in every individual function, and never allow one to throw, instead returning either an Error (if something went wrong) or the desired return value (if no exception occurred). Here's an example of how I do it (comments included):
TS Playground
If you aren't familiar with TypeScript, you can see the JavaScript-only version of the following code (with no type information) at the TypeScript Playground link above (on the right side of the page).
// This is the code in my exception-handling utility module:
// exception-utils.ts
export type Result <T = void, E extends Error = Error> = T | E;
export function getError (value: unknown): Error {
return value instanceof Error ? value : new Error(String(value));
}
export function isError <T>(value: T): value is T & Error {
return value instanceof Error;
}
export function assertNotError <T>(value: T): asserts value is Exclude<T, Error> {
if (value instanceof Error) throw value;
}
// This is how to use it:
// main.ts
import {assertNotError, getError, isError, type Result} from './exception-utils.ts';
/**
* Returns either Error or string ID,
* but won't throw because it catches exceptions internally
*/
declare function getStringFromAPI1 (): Promise<Result<string>>;
/**
* Requires ID from API1. Returns either Error or final number value,
* but won't throw because it catches exceptions internally
*/
declare function getNumberFromAPI2 (id: string): Promise<Result<number>>;
/**
* Create version of second function with no parameter required:
* Returns either Error or final number value,
* but won't throw because it catches exceptions internally
*
* The previous two functions work just like this, using the utilities
*/
async function fetchValueFromAPI2 (): Promise<Result<number>> {
try {
const id = await getStringFromAPI1(); // Error or string
assertNotError(id); // throws if `id` is an Error
return getNumberFromAPI2(id); // Error or number
}
catch (ex) {
return getError(ex);
}
}
async function doSomethingWithValueFromAPI2 (): Promise<void> {
const value = await fetchValueFromAPI2(); // value is number or Error
if (isError(value)) {
// handle error
}
else console.log(value); // value is number at this point
}
Express lets the developer chain multiple functions as handlers to a single route. From docs:
More than one callback function can handle a route (make sure you
specify the next object). For example:
app.get('/example/b', function (req, res, next) {
console.log('the response will be sent by the next function ...')
next()
}, function (req, res) {
res.send('Hello from B!')
})
This is great if the developer wants to make validations before proceeding to the final function. That's why middlewares are a thing.
Socket.io, on the other hand, only accepts a single handler.
From #types/socket.io:
on( event: string, listener: Function ): Namespace;
It means I can't have middlewares that are event-specific. I know about io.use for global middlewares, and there's an option to have a middleware per namespace too, but all I want is per-event.
My workaround variations
Option 1: try and catch in every event handler.
try {
validateCurrentPlayer(socket);
} catch (e) {
return handleFailedValidation(socket, e);
}
// ... rest of the code
Pro: readable. Con: super repetitive. It means that every relevant entry point with start with the same 5 lines of code that does exactly the same thing every time.
And if the "middleware" returns values, this is how it looks:
let foo: Something;
try {
[foo] = validateCurrentPlayer(socket);
} catch (e) {
return handleFailedValidation(socket, e);
}
// ... rest of the code, use foo
Option 2: Common validation with conditional return
const validation = validate(socket, () => validateCurrentPlayer(socket));
if (validation.error) {
return;
}
const [foo] = validation.result;
This is validate:
export function validate<T extends (...args: any) => any>(socket: Socket, func: T): {
error: boolean;
result: ReturnType<T>;
} {
let result: ReturnType<T> = null;
let error = false;
try {
result = func();
} catch (error) {
handleFailedValidation(socket, error);
}
return {
result,
error,
};
}
As you can see it just wraps the try and catch.
Pro: non-repetitive. Con: Still X lines of code to be copy-pasted into a few handlers.
I don't like these workarounds.
I'm desperately trying to find something similar to the approach of Express, So I could just conditionally call next() if the validation succeeded.
Do you know of any way to do it?
Thanks!
I have following service in TypeScript that fetches data from the backend.
As a parameter of function getAllPropertiesByAppId I have a success and error callback.
export class PropertyService implements IPropertyService {
/**
* Get all properties
*/
public getAllPropertiesByAppId(appliactionId: string, success: (properties: Array<IPropertyItem>) => void, error: (error: any) => void): void {
//set up createRequestStr and requestInit
fetch(createRequestStr, requestInit)
.then<IPropertyItem[]>((response: Response) => {
if (response.status===401) {
throw new UnAuthorizedException();
}
return response.json<IPropertyItem[]>();
})
.then((response: IPropertyItem[]) => {
success(response);
})
.catch((reason: any) => {
//error handling
}
});
}
Then I am using this service in my action creator:
initProperties: (appId: string): ActionCreator => (dispatch: Redux.Dispatch, getState: () => IApplicationState) => {
"use strict";
console.log("ShoppingCart initProperties - Request all Property");
var service: IPropertyService = kernel.get<IPropertyService>("IPropertyService");
service.getAllPropertiesByAppId(appId, (properties: Array<IPropertyItem>): void => {
dispatch(new ShoppingCartPropertiesLoaded(appId, properties));
dispatch(new ShoppingCartPropertiesDone(appId, System.Init.Done));
}, (error: any): void => {
console.log("ShoppingCart initProperties - error:" + error);
dispatch(new ShoppingCartPropertiesDone(appId, System.Init.Error));
});
}
So when I call initProperties action creator it calls getAllPropertiesByAppId and when everything is fine I will dispatch actions ShoppingCartPropertiesLoaded and ShoppingCartPropertiesDone.
I have simple component connected to store and the component will throw errors when the render method is executing
export default class TotalPriceList extends React.Component<ITotalPriceListProps, void> {
public render(): JSX.Element {
throw 'SomeError';
}
}
The unhandled exception ends up in the catch statement of fetch.
Have I missed something like how to exit promise correctly or even better how to call function/callback from than statement and exit promise, to avoid catch exception from callback in catch statement of fetch?
Thanks very much for your help
As a parameter of function getAllPropertiesByAppId I have success and error callback.
And that's your actual problem. You should always return a promise from an asynchronous function.
Stop using callback parameters!
Your code should read
/**
* Get all properties
*/
public getAllPropertiesByAppId(appliactionId: string): Promise<IPropertyItem[]> {
//set up createRequestStr and requestInit
return fetch(createRequestStr, requestInit)
// ^^^^^^
.then<IPropertyItem[]>((response: Response) => {
if (response.status===401) {
throw new UnAuthorizedException();
}
return response.json<IPropertyItem[]>();
});
}
This will incidentally solve your problem with unhandled rejections. By not ending the chain but returning the promise, you put the responsibility of handling errors on the caller - as usual. Also the caller is implicitly responsible for anything he does in his promise callbacks - they don't concern the promise-returning method at all.
You'd therefore use
service.getAllPropertiesByAppId(appId).then((properties: Array<IPropertyItem>): void => {
// ^^^^^
dispatch(new ShoppingCartPropertiesLoaded(appId, properties));
dispatch(new ShoppingCartPropertiesDone(appId, System.Init.Done));
}, (error: any): void => {
console.log("ShoppingCart initProperties - error:" + error);
dispatch(new ShoppingCartPropertiesDone(appId, System.Init.Error));
}).catch((reason: any) => {
// error handling for a failed dispatch
});
If you do not want to catch an exception inside a promise chain, you only have to remove the .catch call at the end.
But keep in mind that you will not be able to catch this error with a try {} catch (error) {} block. Instead, it will bubble up to the point where you will receive a unhandledRejection at the top level.
If I understood correctly, you are passing a callback (success) to getAllPropertiesByAppId, which returns a promise that calls the callback; fetch(…).then(success) basically. So what you experience is completely to the defined behaviour of exceptions inside functions enclosed in promises.
You might want to switch to using promises all the way instead of mixing continuation passing style (callbacks) and promises.
Something like (in pseudo-code-js, not ts sorry)
class Service {
getAllPropertiesByAppId (appId) {
return fetch(createRequestStr, requestInit)
.then(response => response.json());
}
};
// …
service.getAllPropertiesByAppId(…)
.then(dispatchAllTheThings)
.catch(error => { console.log(…); dispatch(…) })
Throwing a exception inside a dispatched component will then be caught inside the .catch in the promise chain after the call to dispatchAllTheThings.
I use this to test that the service adds an item to observable stream of errors.
it('can be subscribed for errors', () => {
let testError = new Error('Some error.');
let called = false;
let subscription = service.onError.subscribe(error => {
called = true;
expect(error).toEqual(testError);
});
/// This makes an error to be added to onError stream
service.setError(testError);
expect(called).toEqual(true);
});
I use the called variable to make sure that the subscription callback was actually called. Otherwise the test would pass when it shouldn't. But it doesn't seem right to me. Also, it wouldn't work if the stream was asynchronous.
Is this a good way to test that? If not, how to do it properly?
EDIT: this is the class that's being tested. It's in typescript, actually.
import { ReplaySubject } from 'rxjs/Rx';
export class ErrorService {
private error: Error;
public onError: ReplaySubject<Error> = new ReplaySubject<Error>();
constructor() {
}
public setError = (error: Error) => {
this.error = error;
console.error(error);
this.onError.next(error);
}
public getError() {
return this.error;
}
public hasError() {
return !!this.error;
}
}
The way you are testing is good. You are:
Checking if the value is correct with expect statement.
Checking the fact the expect statement is being executed.
Especially the last part is important otherwise the expect might not be triggered and the test will falsely pass.