Canonical way to exit async scripts with non-zero code - javascript

Say you have a script that looks like
async function main() {
await somethingThatCanFail()
}
main()
Currently to exit with a non-zero code I'm doing
main()
.then(() => console.log('success message and stuff')
.catch(function(err) {
console.error(err)
process.exit(1)
})
because throw new Error(err) is futile inside of a promise. However, this feels kinda hacky, and I'm wondering if there's a more standard way to do this.

process.exit() is exactly how you should end your process if you want to end it. Nothing "hacky" about that at all.
You properly fielded an error, decided you wanted to abort the process because of that error and ended the process in a documented manner. I would consider an uncaught exception a lot more hacky.
You can output to the console anything you want to for diagnostic purposes before exiting if that's what you thought you might be missing from an unhandled exception.

Related

How to catch an error thrown in an un-awaited async function inside try/ catch?

I'm hoping someone can suggest a better method for what I'm trying to achieve.
While performing a rather long and complicated webflow using puppeteer, occasionally an error will occur that disrupts the actions I'm trying to take on the page. There's nothing I can do about it in these situations other than return a useful error. However it doesn't happen often enough to explicitly wait and try to catch it after each step in my code.
The solution I have is:
async function runCode() {
try {
const page = browser.open(url)
listenForError(page)
await longWebFlow()
} catch (err) {
return err.message
}
}
async function listenForError(page) {
await page.waitForXPath(errorMessageXPath)
throw new Error('Error found!')
}
try {
await runCode()
} catch (err) {
console.log(err.message)
// should print('Error found')
}
Obviously, the unawaited listenForError call won't be caught in the try/catch, but I also cant await the call, or else I'll never get to the main part of the code.
The code works to short-circuit the process and return, since the error occurred, but how can I refactor this to catch the error message?
It seems like you want to wait until either the error is thrown or the longWebFlow() finishes - basically doing both concurrently. That's a perfect use case for promises with Promise.race:
async function runCode() {
const page = browser.open(url)
await Promise.race([
listenForError(page),
longWebFlow(),
]);
}
You could also use Promise.all if you made longWebFlow cancel the listenForError and fulfill the promise.
Either way, since you can properly await the promises now, the error also will be caught in the try/catch, as it should.
If you can't await the async operation then the only other "follow up" is with callbacks like .then() and .catch(). For example, you can catch the error here:
listenForError(page).catch(e => console.log(e));
This won't await the operation, it's just supplying a callback for whenever that operation fails at whatever point in the future.

Exiting the process after uncaughtException

In my NodeJS app, whenever it encounters an unhandled exception, I want the process to exit.
However, it seems like according to NodeJS's docs, it does exit by default.
So my question is - should I have something like this in my code?
process
.on('uncaughtException', (err) => {
log.error('uncaughtException:', err.message);
log.error(err.stack);
process.exit(1);
});
or should I leave the process.exit(1) part out of it because it's not needed?
The part process.exit(1) is needed, it does not exit by default and your application may terminate in an unexpected state so you should put process.exit(1) in uncaughtException
However, it is also a good practice to put event unhandledRejection (if you are using Promises in your app) so you better understand what has happened.
process
.on('unhandledRejection', (reason, p) => {
console.error(reason, 'Unhandled Rejection at Promise', p);
})
.on('uncaughtException', err => {
console.error(err, 'Uncaught Exception thrown');
process.exit(1);
});
Moreover as a side-note(Dario mentions, and quotes official documentation here, see documentation)
The correct use of 'uncaughtException' is to perform synchronous
cleanup of allocated resources (e.g. file descriptors, handles, etc)
before shutting down the process.

Handling Errors early in the Promise Chain

I'm trying to find a general way to handle errors on promise chains. In the following snipped I'd like to handle an potential connection-error directly in my connection.js.
connection.js
function getData() {
return fetch(myEndpoint)
.catch(err => handleConnectionError(err)) // redirect to error page or similar
}
app.js
// import connection
connection.getData()
.then(data => handleData(data.foo))
So there are two ways this scenario could play out:
If I catch the error in connection.js without throwing a new one, it will continue the promise chain and handleData() will fail
If I throw an Error again after handling it, the Promise chain wont be executed any further, but then I have an unhandled-promise-rejection error in the console.
So is there actually no better way, than catching and handling the errors everytime I'm using the getData() function somewhere in my app?
The best way to go about this would be to have a final catch to take care of all errors. E.G:
function errorHandler(e) {
if (e instanceof x) {
//handle here
}
}
And then:
fetch(endPoint).then(doSomething).then(doAnotherThing).catch(err => errorHandler(err))
If fetch or any other promise along the chain produces an error, the rest of the then() statements will be skipped and will be catched by the final catch function. From there you will need to make sure that all types of errors that could be thrown by all promises are taken care of.
However, you will need to get rid of the catch in getData as it will cause the rest of the chain to run as usual since the error is taken care of.
Hope that helps
So is there actually no better way, than catching and handling the errors everytime I'm using the getData() function somewhere in my app?
Yes, that's precisely what you should do:
function getData() {
return fetch(myEndpoint)
}
// elsewhere:
connection.getData().then(handleData, handleConnectionError);
You usually don't know whether you always want to "redirect to error page or similar". Maybe at some places in your app, you can handle connection errors by faking response data, and in other places you might want to show an error message instead of triggering a redirect.

What is the difference between using Promise.catch() and wrapping a Promise in try...catch?

I was sitting in listening to a Javascript class today and they were covering something I hadn't seen before and which I don't fully understand. I will try to reproduce as best I can from memory
Instead of using the catch of a Promise to handle errors, which I'm used to, the teacher used try...catch wrapped around the Promise and its thens. When I asked him why he did this, he said it was to catch the error 'synchronously'. That is, instead of the following format (I'm using pseudocode), which I'm used to
someLibrary.someFunctionThatReturnsAPromise
.then(() => something)
.then(() => somethingElse)
.catch(err => reportError)
he did it thus
try {
someLibrary.someFunctionThatReturnsAPromise
.then(() => something)
.then(() => somethingElse)
}
catch(err) {
reportError
}
What would be the difference between these two ways of catching the error?
How would wrapping a Promise, which is asynchronous, report errors in a synchronous fashion?
Thanks for any insights!
The try-catch won't catch asynchronous errors around a <somePromise>.then since as you've noticed, the block will exit before the promise has finished/potentially thrown.
However, if you are using async/await then the try-catch will catch since the block will wait for the await:
async function foobar() {
try {
await doSomePromise();
} catch (e) {
console.log(e);
}
}
The try/catch version will only catch the error if the error is thrown when the initial (synchronous) code is running - it will not catch errors that are thrown inside any of the .thens:
try {
Promise.resolve()
.then(() => {
throw new Error()
});
} catch(e) {
console.log('caught');
}
So, the only way an error will be caught with try/catch in your code is if someLibrary.someFunctionThatReturnsAPromise throws synchronously. On the other hand, the .then/.catch version will catch any error (and is almost certainly preferable).
catch after then (first example)
it is a short way to to handle only error from your promise. It is the same as
.then(null, (err) => reportError)
as then takes two parameters: for fulfilled and rejected state of the promise.
try catch block (second example)
in short - is is a common way to handle your code block. if you what from your code not only failed but do something after it. Try/catch is working only in sync mode, that is why if you work with async code, you should wrap it, for example in async/await way.
Actually you can also you use 3 statement after catch - finally, but it depends )

JS bluebird promise error gives no detail

I'm using bluebird promise with Node.js, after a long piece of code Promise caught an error and when I tried to print out the error message, all it said was:
[ReferenceError: i is not defined]
Is there anything I can do to get more details of the error, like the number of line that made this happen?
While using Promise, I tend to catch error like so:
return User.findOne({
id: someId
}).then(function(foundUser) {
// do something
}).catch(function(err) {
console.log(err);
});
If I just remove the catch mechanism, Node.js will print out all the error stack trace for me...

Categories

Resources