How to catch exception in async context - javascript

I'm facing a problem of exception catching when the context is async.
Here is my minimal reproducible code
const { EventEmitter } = require('events');
const delaySync = function(ttd) {
return new Promise((resolve) => {
setTimeout(() => resolve(0), ttd);
});
};
const myEvent = new EventEmitter();
myEvent.on('something', async () => {
console.log('Event triggered');
// Just delay for 100ms and then throw
await delaySync(100);
throw new Error('Something happened');
});
describe('Events', () => {
it('should catch error', async () => {
await expect(myEvent.emit('something')).rejects.toThrow('Something happened');
});
});
Basically, I have an event emitter. It has a callback attached on event 'something' which is async. There are some async jobs inside and after 100ms delay it throws an error.
Normally I'd catch such errors in jest using await expect(fn).rejects.toThrow(...) however this case is different because expect fn expects a promise and myEvent.emit('something') is regular non-promise context.
In this case, I'm getting an error
● Events › should catch error
expect(received).rejects.toThrow()
Matcher error: received value must be a promise or a function returning a promise
Received has type: boolean
Received has value: true
...
(node:20242) UnhandledPromiseRejectionWarning: Error: Something happened
I've tried it with try-catch block as well but it did not work (not able to catch at all);
try {
myEvent.emit('something');
await delaySync(250);
} catch (e) {
expect(e.message).toBe('Something happened');
}
I've tried
// Not throwing because exception is not happening right away
expect(() => myEvent.emit('something')).toThrow('Something happened');
Also this
expect(async () => {
// Will thow after 100ms
myEvent.emit('something');
await delaySync(250);
}).toThrow('Something happened');
But no luck with any of them. 😕
Any ideas on how can I catch the error in this case? or any workaround?

The Eventemitter is fully sync and there is no workaround for it. What you can do is to use the eventemitter2 module to replace the original Eventemitter class. It is fully compatible with Eventemitter and allows you to use async actions.
var EventEmitter = require('eventemitter2');
const delaySync = function (ttd) {
return new Promise((resolve) => {
setTimeout(() => resolve(0), ttd);
});
};
const myEvent = new EventEmitter();
myEvent.on('something', async () => {
console.log('Event triggered');
// Just delay for 100ms and then throw
await delaySync(100);
throw new Error('Something happened');
// for async emitters pass promisify true.
}, { promisify: true });
describe('Events', () => {
it('should catch error', async () => {
await expect(myEvent.emit('something')).rejects.toThrow('Something happened');
});
}

Related

Try...catch vs .catch

So I have this user service functions in service.ts which includes database stuffs.
export const service = {
async getAll(): Promise<User[]> {
try {
const result = await query
return result
} catch (e) {
report.error(e)
throw new Error(e)
}
},
...
}
And query.ts file for some reasons, eg: caching, business logic, etc.
export const query = {
async index(): Promise<User[]> {
try {
const result = await service.getAll()
return result
} catch (e) {
report.error(e)
throw new Error(e)
}
},
...
}
And another upper layer for routers and resolvers, because I want to see all routes in one file.
export const resolver = {
Query: {
users: (): Promise<User[]> => query.index(),
},
...
}
Do I need to wrap try...catch in all functions? Or can't I just add .catch at the very top layer like this:
export const resolver = {
Query: {
users: (): Promise<User[]> => query.index().catch(e => e),
},
...
}
Do I need to wrap try...catch in all functions?
No, you don't, not unless you want to log it at every level for some reason. Just handle it at the top level.
In an async function, promise rejections are exceptions (as you know, since you're using try/catch with them), and exceptions propagate through the async call tree until/unless they're caught. Under the covers, async functions return promises and reject those promises when a synchronous exception occurs or when a promise the async function is awaiting rejects.
Here's a simple example:
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function outer() {
// ...
await delay(10);
console.log("before calling inner");
await inner();
console.log("after calling inner (we never get here)");
}
async function inner() {
// ...
await delay(10);
console.log("inside inner");
// Something goes wrong
null.foo();
}
outer()
.catch(e => {
console.log("Caught error: " + e.message, e.stack);
});
Just as a side note: If you do catch an error because you want to do X before the error propagates and you're going to re-throw the error after you do X, best practice is to re-throw the error you caught, rather than creating a new one. So:
} catch (e) {
// ...do X...
throw e; // <== Not `throw new Error(e);`
}
But only do that if you really need to do X when an error occurs. Most of the time, just leave the try/catch off entirely.

Exit, reset, or clear JavaScript call stack without throw Error?

Given a number of nested promises structured similarly to the example below, is it possible, given a timeout result in delta() to neither return, resolve, nor reject the timeout, and not throw Error(timeout), but somehow exit the JavaScript call stack and reset to a new screen?
Context is React Native app
Nav library is react-native-navigation
Goal is to not throw Error(timeout)
We do not want the error to bubble up through the promise chain
The promise nesting occurs in unique variations in 100s of places in the app
We do not want individually handle a timeout response/error in each spot
Hoping instead to somehow exit the call stack and reset the screen
react-native-navigation has a slew of methods to setRoot, navigateToScreen, etc, but these don't exit call stack
const alpha = async () => {
try return await bravo()
catch (e) throw Error(e)
}
const bravo = async () => {
try return await charlie()
catch (e) throw Error(e)
}
const charlie = async () => {
try return await delta()
catch (e) throw Error(e)
}
const delta = async () => {
try {
const res = await API()
if (res === timeout) // clearCallStackAndResetToNewScreenDoNotThrowError() POSSIBLE?
else return res
} catch (e) {
throw Error(e)
}
}
const one = async () => {
try {
await alpha()
} catch (e) {
uniqueErrorHandling(e)
}
}
const two = async () => {
try {
await alpha()
} catch (e) {
uniqueErrorHandling(e)
}
}
one()
two()
etc ...

Node.js / MongoDB: can't catch error from Cursor.map callback outside of this callback

mongodb 3.6.3
node 8.10.0
I discovered this accidentally and after some time researching problem still can't figure it out. My code has global error handler that should catch all errors, but error that originated from find().map callback was skipped by it and process was exited with standard error log to console.
Here is test code that i come up with
(async () => {
const {MongoClient} = require('mongodb');
const uri = 'your uri';
const connection = MongoClient.connect(uri, {useNewUrlParser: true});
connection.catch((e) => {
console.log('inside connection.catch');
console.log(e);
});
const collection = (await connection).db().collection('collection');
const findPromise = collection.find({}).limit(0).skip(0);
const functionWithError = (item) => {
throw new Error('functionWithError');
};
// This fails to catch error and exits process
// try {
// const items = await findPromise.map(functionWithError).toArray().catch((e) => console.log('Promise catch 1'));
// console.log(items);
// } catch (e) {
// console.log('map error 1');
// console.log(e);
// }
// This (obviously) works and 'inside map error' is written to console
try {
const items = await findPromise.map(() => {
try {
functionWithError();
} catch (e) {
console.log('inside map error'); // this will be outputted
}
}).toArray().catch((e) => console.log('Promise catch 2'));
console.log(items);
} catch (e) {
console.log('map error 2');
console.log(e);
}
})();
I don't see any problem in code and expect 'Promise catch 1' or 'map error 1' to be logged to console. So whats the problem? Thanks in advance.
Its about the scope of asynchronous function. If you try to use asynchronous function in try..catch block, asynchronous function goes out of scope of try..catch block, so it, it is always good practice to return errors in asynchronous function callback, which can be handled by simple if..else check.
Useful link
Example1: throwing an error in async-await, where no asynchronous process is running.
(async () => {
const someAsync = async () => {
throw new Error('error here');
};
try {
await someAsync();
} catch (e) {
console.log('error cached as expected!');
}
console.log('execution continues');
})();
Example2: throwing an error in async-await, where the asynchronous process is running.
(async () => {
const someAsync = async () => {
let timeInMS = 0; // 0 milliseconds
let timer = setTimeout(function() {
clearTimeout(timer);
throw new Error('error here');
}, timeInMS);
};
try {
await someAsync();
} catch (e) {
console.log('error cached as expected!');
}
console.log('execution continues');
})();

Bluebird cancellation with await

This code is working correctly:
let promise;
try {
promise = parent();
//but I want: await parent();
await continueStack();
} catch (e) {
console.log('Caught error in endpoint');
}
eventBus.on('stop::all', () => {
console.warn('Caught stop::all event');
promise.cancel();
});
It's cancelling promise on event emitted from other part of program. My problem is that I can not have await parent() because then listening for event is not executed yet. If I change to this:
let promise;
eventBus.on('stop::all', () => {
console.warn('Caught stop::all event');
promise.cancel();
});
try {
promise = await parent();
await continueStack();
} catch (e) {
console.log('Caught error in endpoint');
}
then I have error
TypeError: Cannot read property 'cancel' of undefined
and it crash and continueStack() is never executed. How am I suppose to reconcile these?
I've created entire example with above scenerio:
https://gist.github.com/BorysTyminski/147dc2e0c44c8d64386253d563ff6db4
In order to run it you need to get both files, install package.json dependiecies and run cURL / Postman request GET to localhost:5000/test
You can do this by setting the promise var to parent, and then awaiting it:
const EventEmitter = require('events');
const Promise = require('bluebird');
Promise.config({
cancellation: true,
})
const parent = new Promise(resolve => {
setTimeout( () => {
resolve();
}, 100);
});
const continueStack = new Promise(resolve => {
setTimeout( () => {
resolve();
}, 100);
});
let promise;
const emitter = new EventEmitter();
emitter.on('stop', function() {
if(promise && promise.cancel){
promise.cancel();
console.log('promise cancelled');
} else {
console.log('somehow promise was not properly set at this point');
}
});
setTimeout(_ => {
emitter.emit('stop');
}, 80);
async function operation() {
try {
promise = parent;
await promise;
console.log('after promise. This log should never appear.');
await continueStack;
console.log('after wait 2. This log should never appear.');
} catch(e) {
console.log('Even if the promise is cancelled, this is not called.');
}
}
operation();
(running that snippet with node will only log 'promise cancelled'): Nothing after await promise inside the async function is executed).
Some considerations:
Promise.config is needed to set cancellable to true.
await is not like a sleep. It does not block the javascript thread nor the event listener, or any event emitter. It just ensures that code following an await will wait for the awaited promised. But this only in the context of an async function.
cancelling a Promise does not make the catch handler to fire. This puzzles me but I guess it is just how it works.

How to assert an async method throwing Error using toThrow with Jest

I have seen this question which expects a Promise to work. In my case the Error is thrown before and outside a Promise.
How can I assert the error in this case? I have tried the options below.
test('Method should throw Error', async () => {
let throwThis = async () => {
throw new Error();
};
await expect(throwThis).toThrow(Error);
await expect(throwThis).rejects.toThrow(Error);
});
Calling throwThis returns a Promise that should reject with an Error so the syntax should be:
test('Method should throw Error', async () => {
let throwThis = async () => {
throw new Error();
};
await expect(throwThis()).rejects.toThrow(Error); // SUCCESS
});
Note that toThrow was fixed for promises in PR 4884 and only works in 21.3.0+.
So this will only work if you are using Jest version 22.0.0 or higher.
If you are using an earlier version of Jest you can pass a spy to catch:
test('Method should throw Error', async () => {
let throwThis = async () => {
throw new Error();
};
const spy = jest.fn();
await throwThis().catch(spy);
expect(spy).toHaveBeenCalled(); // SUCCESS
});
...and optionally check the Error thrown by checking spy.mock.calls[0][0].

Categories