I have a few classes which use 'dns' from node.js. But when an error occurs, my app is thrown. I made a siimple example with classes and throwable functions and I faced with the same problem. It's works if an exception is thrown from function but it doesn't work if an exception is thorwn from class.
Example:
class Test {
constructor() {
this.t();
}
async t() {
throw new Error("From class");
}
}
async function test(){
new Test();
}
try {
test().catch(e => {
console.log("From async catch");
});
} catch (e) {
console.log("From try catch");
}
Output:
Uncaught (in promise) Error: From class
at Test.t (<anonymous>:6:11)
at new Test (<anonymous>:3:10)
at test (<anonymous>:11:3)
at <anonymous>:15:3
How to catch errors from try/catch block in this example?
UPD:
Full code (typescript):
export class RedisService {
client: any;
expirationTime: any;
constructor(args: RedisServiceOptions) {
let redisData: any = {};
if (args.password)
redisData["defaults"] = { password: args.password };
dns.resolveSrv(args.host, (err, addresses) => {
if (err) {
/// Handling error in main func
}
else {
log.info("Using Redis cluster mode");
redisData["rootNodes"] = addresses.map(address => {
log.info(`Adding Redis cluster node: ${address.name}:${address.port}`);
return Object({ url: `redis://${address.name}:${address.port}` })
});
this.client = createCluster(redisData);
};
this.client.on('error', (err: Error) => log.error(`Redis error: ${err.message}`));
this.client.connect().then(() => { log.info("Connected to Redis") });
});
this.expirationTime = args.expirationTime;
}
/// Class functions
}
it doesn't work if an exception is thrown from class.
In particular, when an asynchronous error event occurs in the constructor, yes. Like your question title says, you can't handle errors outside of an async context, and a constructor is not that.
Your current implementation has many issues, from client being undefined until it is initialised to not being able to notify your caller about errors.
All this can be solved by not putting asynchronous initialisation code inside a constructor. Create the instance only once you have all the parts, use an async helper factory function to get (and wait for) the parts.
export class RedisService {
client: RedisClient;
expirationTime: number | null;
constructor(client: RedisClient, expirationTime: number | null) {
this.client = client;
this.expirationTime = expirationTime;
}
static create(args: RedisServiceOptions) {
const addresses = await dns.promises.resolveSrv(args.host);
log.info("Using Redis cluster mode");
const redisData = {
defaults: args.password ? { password: args.password } : undefined,
rootNodes: addresses.map(address => {
log.info(`Adding Redis cluster node: ${address.name}:${address.port}`);
return { url: `redis://${address.name}:${address.port}` };
}),
};
const client = createCluster(redisData);
client.on('error', (err: Error) => log.error(`Redis error: ${err.message}`));
await this.client.connect();
log.info("Connected to Redis");
return new RedisService(client, args.expirationTime);
}
… // instance methods
}
Now in your main function, you can call create, use await, and handle errors from it:
async function main(){
try {
const service = await RedisService.create(…);
} catch(e) {
console.log("From async catch", e);
}
}
You generate multiple async-requests but you can only catch errors from the first one:
You create a promise with async function test().
Then you create a syncronous call within it, with new Test(), every error syncronously thrown from within it will be catched by the catch.
Then you generate another promise call from within the syncronous constructor, this error can't be caught by the try/catch-block or .catch at, or above the async function test().
It is similar to this:
constructor() {
new Promise(() => throw new Error(''))
}
So you have 3 possible ways to solve it:
You catch the error inside the async t() {}-call.
You catch it inside the constructor with this.t().catch(console.error) (which can't be forwarded to the try/catch block) as it is forwarded to the catch block of the Promise behind the async call. And if there is no .catch on the async call, you get the "Unhandled Promise rejection"-Error.
Don't call the async function from within the constructor at all, use it like this:
class Test {
async t() {
throw new Error("From class");
}
}
async function test(){
await (new Test()).t();
}
try {
test().catch(e => {
console.log("From async catch");
});
} catch (e) {
console.log("From try catch");
}
Don't make async methods)))
Your solution looks somewhat like this.
class Test {
constructor() {
this.t();
}
t() {
(async () => {
try {
throw new Error("From class");
} catch (e) {
console.log(e);
}
})();
}
}
Have a nice rest of your day
Related
I have a basic stringify function that looks like this ->
export const stringify = <T>(value: T) => {
try {
return JSON.stringify(value);
} catch(error){
return ''
}
}
I want to write a test that can cover the catch block of the function.
I've tried adding such a test ->
it('should be able to check for errors', async () => {
await expect(stringify('')).rejects.toThrow()
})
But this test keeps throwing errors about the function not being a promise. The function isn't going into the catch block at all.
The main function isn't a promise so I can't use the promise functions of jest.
How do I test the catch block?
There is no need to use async/await in this test. Also when there is an error you are returning '' from catch block, meaning your function will not throw anything.
Something like will work for your case
it('should be able to check for errors', () => {
expect(stringify(<error value>)).toBe('')
})
Expect the function definition to Throw
const functionDef = () => {
throw new TypeError("Error Message");
};
test("Test description", () => {
expect(functionDef).toThrow(TypeError);
expect(functionDef).toThrow("Error Message");
});
I have the following simplified code:
class LocationService {
const location$: BehaviorSubject<Location> = new BehaviorSubject(undefined);
constructor() {
location$.pipe(throttleTime(3000)).subscribe((location) => {
this.trackNewLocation(location);
});
}
private async trackNewLocation(location: Location): Promise<void> {...}
public trackLocation(location: Location) {
this.location$.next(location);
}
}
Function trackNewLocation is an async function communicating with backend to save the new location to database. It can throw exception for instance when no connection.
Using rxjs is because I need to control the pace of location saving, e.g. every 3 seconds.
My question is when a client calling public function trackLocation, how can it try/catch the error thrown by the private function trackNewLocation?
Since an async function returns a promise, you can refer to here on how to catch an error.
As shown in the mozilla docs (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch)
const promise1 = new Promise((resolve, reject) => {
throw 'Uh-oh!';
});
promise1.catch((error) => {
console.error(error);
});
You would use the .catch function to catch an error
I have a custom error that I call inside try. And I want to return this error inside catch without throw a new one.
const callSomething = async () => {
try {
doSomething();
} catch (error) {
// This function receive the error with the additional properties, so we need the custom error object
customErrorTreatment(error);
}
};
This function is where the error is first call.
const doSomething = async () => {
try {
// This function throw a custom error class with additional properties
throwApiError({
responseMessage: 'Some error occour',
responseStatus: 500,
});
} catch (error) {
// If I return error, the function callSomething just receive the value without error.
return error;
// I can call throwApiError again, but feels ugly, that is the only way?
return throwApiError({
responseMessage: error.responseMessage
responseStatus: error.status,
});
}
};
This is the custom error class and function
export const ApiError = class ApiError extends Error {
constructor({ responseMessage, responseStatus, error }) {
super(error);
this.responseMessage = responseMessage;
this.responseStatus = responseStatus;
}
};
const throwApiError = ({ responseMessage, responseStatus, error }) => {
throw new ApiError({ responseMessage, responseStatus});
};
Don't call throwApiError() again. Just throw error if you want the promise to stay rejected - that's how promises work.
Or get rid of your catch() handler entirely so the error just propagates naturally back up to a higher level without your intervention. You don't appear to be doing anything in the catch handler so perhaps you can just remove it.
In short No, because to generate an error you need to throw, and your method is a common method for error handling. but there is another way to manage errors like this:
const callSomething = async () => {
let { result, error } = resdoSomething();
if (error) {
return throwApiError({
responseMessage: error.responseMessage
responseStatus: error.status,
});
}
console.log(result)
// do somethings
};
and
const doSomething = async () => {
try {
let result = myfunction()
return {result: result , error : null}
} catch (error) {
return {result : null, error: error};
}
};
In this way you can reduce the number of try/catch
I'd like to convert this Ember route action to use ES2017 async / await. Can someone please explain what this would look like?
Per spec, I already added: babel: { includePolyfill: true } to my ember-cli-build.js file:
save() {
let tenant = this.modelFor(this.routeName).tenant;
let app = this.modelFor(this.routeName).app;
return tenant.save().then(()=> {
return tenant.get('app').save({ adapterOptions: { tenantId: tenant.id }}).then(() => {
this.transitionTo('tenants.tenant.info', tenant.id);
}).catch((error) => {
tenant.get('app').rollback();
throw error;
});
}).catch((error) => {
tenant.rollback();
throw error;
});
}
Your code, converted to async/await:
async save() {
let tenant = this.modelFor(this.routeName).tenant;
let app = this.modelFor(this.routeName).app;
try {
await tenant.save();
try {
await tenant.get('app').save({ adapterOptions: { tenantId: tenant.id }});
this.transitionTo('tenants.tenant.info', tenant.id);
} catch (error) {
tenant.get('app').rollback();
throw error;
}
} catch (error) {
tenant.rollback();
throw error;
}
}
To convert from promises, you add the await keyword to method calls that return promises. Everything that you place in the then method of the promise you can simply put after the await statement.
The promises' catch methods convert to regular try/catch blocks.
Very similar to the answer Patrick Hund wrote, but attaching the catch statements to the promises being awaited, rather than wrapping in try/catch blocks, and extracting the error logic into a single function.
async save() {
let tenant = this.modelFor(this.routeName).tenant;
let app = this.modelFor(this.routeName).app;
await tenant.save().catch(handleError.bind(this, false));
await tenant.get('app').save({ adapterOptions: { tenantId: tenant.id }})
.catch(handleError.bind(this, true));
this.transitionTo('tenants.tenant.info', tenant.id);
// isApp is bound via bind; error will be provided when called via catch
function handleError(isApp, error) {
if (isApp) {
tenant.get('app').rollback();
} else {
tenant.rollback();
}
throw error;
}
}
If I add the keyword async to a function, it seems that I have to catch errors "in" that function. Sometimes it doesn't make sense to catch errors and I want to defer them to the caller as I may not know the context in which the function is called (e.g. is the caller doing res.json(e) or next(e), or neither)
Is there a way around this? So I can use async (in order to await inside the function) and defer errors to the caller?
Here is a really contrived example
https://codepen.io/anon/pen/OzEXwM?editors=1012
try {
function example(obj = {}) {
try {
obj.test = async () => { // if I remove async keyword, it works, otherwise I'm forced to catch errors here
//try{
throw new Error('example error') // I know `example outer error` won't catch as the returned object loses the context
//}catch(e){
// console.log('I do not want to catch error here'), I wan't to defer it to the caller
//}
}
return obj
} catch (e) {
console.log('example outer error')
}
}
let doit = example()
doit.test() // why doesn't 'outer error' catch this?
} catch (e) {
console.log('outer error')
}
The script ran as is will give an Uncaught Exception. But if I remove the async keyword it works (yes, I know in this example, async is silly, it's just an example)
Why can't I catch the error when doit.test() is called?
Usually, I would just comply and rework, but in trying to explain to someone else this morning, I realized, I didn't really know the answer.
Why can't I catch the error when doit.test() is called?
Because it is async. By the time it has reached the throw error part the try catch block on the outside has already been executed and passed. There is nothing to throw to so to speak.
To fix this, since async and await are just syntactic sugar for Promises you just use it's catch callback. Your test() function is going to return a promise so just add a the callback onto the returned promise
doit.test().catch(()=>{
console.log('Any uncaught exceptions will be sent to here now');
});
Demo
function example(obj = {}) {
obj.test = async() => {
throw new Error('example error')
}
return obj;
}
let doit = example()
doit.test().catch(e => {
console.log('Caught an exception: ', e.message);
});
If you want to catch error of test() function. You need to do await doit.test()
https://jsfiddle.net/1tgqvwof/
I wrapped your code in a anonymous function since await must be within async function
(async function () {
try {
async function example(obj = {}) {
try {
obj.test = async () => { // if I remove async keyword, it works, otherwise I'm forced to catch errors here
//try{
throw new Error('example error') // I know `example outer error` won't catch as the returned object loses the context
//}catch(e){
// console.log('I do not want to catch error here'), I wan't to defer it to the caller
//}
}
return obj
} catch (e) {
console.log('example outer error')
}
}
let doit = example()
await doit.test() // why doesn't 'outer error' catch this?
} catch (e) {
console.log('outer error')
}
})();