Error being thrown within async function gets caught - javascript

I want to use an async function instead of returning a promise, however to reject I need to throw an error like this:
async function asyncOperation() {
throw new Error("Terminate this application.");
}
(async () => {
try {
await asyncOperation();
} catch (e) {
console.error(e);
}
console.log("Nope, I am still running!"); // This will be printed regardless
})();
This means that to catch the reject I must put the function call within a try statement. By doing this, instead of terminating the application, I catch the error and I can continue afterwards.
This of course works as you would expect however I want to throw an error to actually terminate the application regardless if the function was called within a try statement or not.
How could I achieve this without checking the error within the catch statement?

I want to throw an error to actually terminate the application
No. Throwing exceptions is done only to be able to catch and handle them. If you really wanted to terminate your application no matter what (which would be a bad, untestable design), you'd just call process.exit() and be done with it.
I might have other errors that I do want to catch.
It sounds like what you actually want is to handle some of the thrown errors but let those that are unhandled terminate the application as usually - unless caught and handled further up in the chain. For that, you'll want to conditionally re-throw the exception:
try {
…
throw Object.assign(new Error("…"), {reason: X});
…
} catch (e) {
if (e.reason == Y)
console.error(e);
else
throw e;
}
console.log("I won't run after X errors!");
Notice that none of this actually has to do with async/await.

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.

Why a promise reject is not catched within a try catch block but produces an Uncaught error?

I'm facing a promise rejection that escapes a try catch block. The rejection causes an Uncaught exception which is something I can not comprehend.
If I add a reject handler in the Promise.then or a Promise.catch, the rejection gets captured. But I was hopping try catch will work in this situation.
What is happening here?
class HttpResponse {
json() {
return Promise.reject('parse error')
}
}
function fetch() {
return new HttpResponse();
}
const res = fetch();
try {
res.json().then(json => {
alert(`json: ${json}`);
}
//,reason => {
// alert(`promise.reject ${reason}`);
//}
)
//.catch(reason => {
// alert(`promise.catch ${reason}`);
//})
} catch (e) {
alert(`catch{} from try catch: ${e}`);
}
Promises have their own mechanism of handling errors with the catch() method. A try / catch block can't control what's happening when chaining methods.
function fetch() {
return Promise.reject('parse error');
}
const res = fetch();
res.then(json => {
console.log(`json: ${json}`);
}).catch((e) => {
console.log(`catch{} from chained catch: ${e}`);
});
However, using async / await changes that. There you don't use the methods of a promise but handle errors with a try / catch block.
function fetch() {
return Promise.reject('parse error');
}
(async function() {
try {
const res = await fetch();
} catch (e) {
console.log(`catch{} from try catch: ${e}`);
}
})();
The technical reason behind this behavior is because a JavaScript promise invokes one of two callback functions for success or failure.
A promise does not emit an Error that is required for the try to work. it is not an Error (or more technically accurate) an instance of Error. It emits and event. You are encouraged to emit a new Error() if you need it to emit one. As pointed out here: https:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
It emits an event that you can set up a handler for: https://developer.mozilla.org/en-US/docs/Web/API/PromiseRejectionEvent –
Finally, await throws and Error as described in the spec: https://tc39.es/ecma262/#await-rejected
Technical reasons behind:
HostPromiseRejectionTracker is an implementation-defined abstract
operation that allows host environments to track promise rejections.
An implementation of HostPromiseRejectionTracker must complete
normally in all cases. The default implementation of
HostPromiseRejectionTracker is to unconditionally return an empty
normal completion.
https://www.ecma-international.org/ecma-262/10.0/index.html#sec-host-promise-rejection-tracker
Basically javascript engines can freely implement this spec. In the case of browsers you don't get the Error captured inside the try/catch because the error is not emitted where you think it should be. But instead it's tracked with this special event that throws the error in the console.
Also on nodejs, this event causes the process to exit if you have node set to exit on unhandled exceptions.
On the other side, if you instead use async/await, the rejection is treated like an 'error' in practical terms. Meaning that the newer async/await feature behaves in a different fashion showing that it is not only syntactic sugar for Promises.
https://tc39.es/ecma262/#sec-throwcompletion
In sum, if you use Promise.then you are forced to provide a reject argument or chain it with .catch to have the rejection captured or else it will reach the console and in case of nodejs to exit the process if configured to do so (I believe new nodejs does this by default).
But if you use the newer async/await syntax you not only have a concise code (which is secondary) but a better rejection handling because it can be properly nested in a try/catch and it will behave like an standard Error.

What is the proper way to handle errors in a promise?

I've been seeing a couple of different patterns for handling errors.
One is like:
let result;
try {
result = await forSomeResult(param);
} catch {
throw new Error();
}
And the other is like:
const result = await forSomeResult(param).catch(() => throw new Error());
I prefer the latter since it looks like a cleaner solution. But I've also heard talks that the first solution is better since there could be some race condition where the .catch doesn't execute before the next command is run.
I was wondering if someone had a technical answer about why one method is better than the other.
First of all, there's no reason to catch and throw or .catch() and throw unless you're going to do something else inside the catch handler or throw a different error. If you just want the same error thrown and aren't doing anything else, then you can just skip the catch or .catch() and let the original promise rejection go back to the caller.
Then, it is generally not recommended that you mix await with .catch() as the code is not as easy to follow. If you want to catch an exception from await, use try/catch around it. .catch() works, it's just not a preferred style if you're already awaiting the promise.
The one technical difference between these two styles:
async function someFunc()
let x = await fn().catch(err => {
console.log(err);
throw err;
});
// do something else with x
return something;
}
And, this:
async function someFunc()
let x;
try {
x = await fn();
} catch(err) {
console.log(err);
throw err;
}
// do something else with x
return something;
}
is if the function fn() you are calling throws synchronously (which it shouldn't by design, but could by accident), then the try/catch option will catch that synchronous exception too, but the .catch() will not. Because it's in an async function, the async function will catch the synchronous exception and turn it into a rejected promise for you automatically for the caller to see as a rejected promise, but it wouldn't get logged or handled in your .catch() handler.
One of the more beneficial cases for try/catch with await is when you have multiple await statements and you don't need to handle errors on any of them individually. Then, you can surround them with one try/catch and catch all the errors in one place.
async function someFunc(someFile) {
let fileHandle;
try {
// three await statements in a row, all using same try/catch
fileHandle = await fsp.open(someFile);
let data = await fsp.read(...);
// modify data
await fsp.write(...)
} catch(err) {
// just log and re-throw
console.log(err);
throw err;
} finally {
// close file handle
if (fileHandle) {
await fileHandle.close();
}
}
}
It depends.
Are you going to be calling multiple asynchronous functions that could error? You can wrap them all in a try/catch and perform common error handling without having to repeat yourself:
try {
result = await forSomeResult(param);
await forSomeOtherResult();
return await finalResult(result);
} catch { //catches all three at once!
throw new Error();
}
Do you only need to handle errors for this one, specific call? The .catch() pattern is fine. There is no race condition to worry about, await waits for the promise to resolve or reject, and this includes all success and failure callbacks attached to the promise. However, in your example, you're catching an error only to throw an empty one - in that case, it may be preferable to simply write this:
const result = await forSomeResult(param);
...and let the error propagate to the caller naturally.
I've seen a mixture of both styles used common enough that I think it's fine either way - they each have a particular strength.

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 )

Node.js await catch not returning

I am busy working with some code that is responing in an unexpected way (to me). It involves handling Node.js promise exceptions.
I have the following function modified so that all it does is fail
function asynFunc() {
return new Promise(function(res, rej) {
rej("GENERIC ERROR");
});
}
The problem comes in when I am trying to call it and handle this error. If I do the following it works as I expect it to, the function fails and executes the catch which returns, stopping the last console log from executing
async function appStart() {
try {
await asyncFunc();
} catch(log) {
console.log(log);
return;
}
console.log("YOU SHOULD NOT SEE THIS");
}
appStart();
If I write the function as follows though, it does not seem to return, it still executes the last console log regardless of the await
async function appStart() {
await asyncFunc().catch(function(log) {
console.log(log);
return;
});
console.log("YOU SHOULD NOT SEE THIS");
}
If this is doing what I think it's doing then the return is returning from the function inside of the catch and not the appStart function itself, or I'm completely wrong in which case I have no idea why it's not returning.
Is there a way to use the second catch method and still have it return from the calling function?
In the second example, you are not returning from the outside function in the catch, you are returning from the catch callback:
await asyncFunc().catch(function(log) {
console.log(log);// ^
return; // | returns from that function
});
This has the effect of catching the error and moving on and returning a new promise resolving to undefined. There is no way to control the return of the outside function from inside the callback. You need to test the result of the async operation from the outside function, which leaves you with try/catch or explicitly testing the result after the promise resolves.
You're right. It's returning only inside the catch callback, not the outer async function. That's why it exits the catch callback and resolves, and then logs "YOU SHOULD NOT SEE THIS". Generally, it's very unreadable if you mix promise then and catch chaining along with async/await and try/catch. Pick one and stick with it, because mixing them may lead to the inability to catch and handle errors seamlessly.

Categories

Resources