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.
Related
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.
The question is simple, but I haven't found the answer anywhere.
When should i use try catch? in the code below I use try catch to handle the return of a request:
async findUsers() {
this.loading = true;
try {
const [users, count] = await this.api.get('/users/list');
this.users = users;
this.totalPages = Math.ceil(parseInt(count) / 10);
}
catch (error) {
this.Messages.requestFailed(error);
}
finally {
this.loading = false;
}
}
Would it be a good practice to use then(...).catch(...)?
The difference is in how you're handing Promises.
If you're using await to handle the Promise then you wrap it in a try/catch. Think of await as a way to make asynchronous operations semantically similar to synchronous operations.
But if you're not using await and are instead handling the Promise by appending .then() to it then you'd append a .catch() to that chain to catch failures from within the asynchronous operation.
Because a try/catch isn't going to catch an exception that happens from within the asynchronous operation if that operation isn't awaited.
When you use promises(asynchronous operations) then you need to handle the exception in case of an error. So In this case you can use .then().catch(err) {}
When you have synchronous code, use try/catch for exception handling.
const errorTest = async() => {
const result = await $.get("http://dataa.fixer.io/api/latest?access_key=9790286e305d82fbde77cc1948cf847c&format=1");
return result;
}
try {
errorTest()
}
catch(err) {
console.log("OUTSIDE ERROR!" + err)
}
The URL is intentionally incorrect to throw an error, but the outside catch() it not capturing it. Why?
If I use then() and catch() instead, it works.
errorTest()
.then(val=> console.log(val))
.catch(err=> console.error("ERROR OCCURRED"))
This works, but the try {..} catch() doesn't. Why?
I keep getting the Uncaught (in promise) error.
async function errorTest() { /* ... */ }
try {
errorTest()
}
catch(err) {
console.log("OUTSIDE ERROR!" + err)
}
Because errorTest is async, it will always return a promise and it is never guaranteed to finish execution before the next statement begins: it is asynchronous. errorTest returns, and you exit the try block, very likely before errorTest is fully run. Therefore, your catch block will never fire, because nothing in errorTest would synchronously throw an exception.
Promise rejection and exceptions are two different channels of failure: promise rejection is asynchronous, and exceptions are synchronous. async will kindly convert synchronous exceptions (throw) to asynchronous exceptions (promise rejection), but otherwise these are two entirely different systems.
(I'd previously written that async functions do not begin to run immediately, which was my mistake: As on MDN, async functions do start to run immediately but pause at the first await point, but their thrown errors are converted to promise rejections even if they do happen immediately.)
function errorTest() {
return new Promise(/* ... */); // nothing throws!
}
function errorTestSynchronous() {
throw new Error(/* ... */); // always throws synchronously
}
function errorTestMixed() {
// throws synchronously 50% of the time, rejects 50% of the time,
// and annoys developers 100% of the time
if (Math.random() < 0.5) throw new Error();
return new Promise((resolve, reject) => { reject(); });
}
Here you can see various forms of throwing. The first, errorTest, is exactly equivalent to yours: an async function works as though you've refactored your code into a new Promise. The second, errorTestSynchronous, throws synchronously: it would trigger your catch block, but because it's synchronous, you've lost your chance to react to other asynchronous actions like your $.get call. Finally, errorTestMixed can fail both ways: It can throw, or it can reject the promise.
Since all synchronous errors can be made asynchronous, and all asynchronous code should have .catch() promise chaining for errors anyway, it's rare to need both types of error in the same function and it is usually better style to always use asynchronous errors for async or Promise-returning functions—even if those come via a throw statement in an async function.
As in Ayotunde Ajayi's answer, you can solve this by using await to convert your asynchronous error to appear synchronously, since await will unwrap a Promise failure back into a thrown exception:
// within an async function
try {
await errorTest()
}
catch(err) {
console.log("OUTSIDE ERROR!" + err)
}
But behind the scenes, it will appear exactly as you suggested in your question:
errorTest()
.then(val=> console.log(val))
.catch(err=> console.error("ERROR OCCURRED"))
You need to await errorTest
const callFunction=async()=>{
try{
const result = await errorTest()
}catch(err){
console.log(err)
}
}
callFunction ()
Note that the await errorTest() function has to also be in an async function. That's why I put it inside callFunction ()
Another Option
const errorTest = async() => {
try{
const result = await $.get("http://dataa.fixer.io/api/latest?access_key=9790286e305d82fbde77cc1948cf847c&format=1");
console.log(result)
}catch(err){
console.log(err)
}
}
I think the fundamental misunderstanding here is how the event loop works. Because javascript is single threaded and non-blocking, any asynchronous code is taken out of the normal flow of execution. So your code will call errorTest, and because the call to $.get performs a blocking operation (trying to make a network request) the runtime will skip errorTest (unless you await it, as the other answers have mentioned) and continue executing.
That means the runtime will immediately jump back up to your try/catch, consider no exceptions to have been thrown, and then continue executing statements which come after your try/catch (if any).
Once all your user code has ran and the call stack is empty, the event loop will check if there are any callbacks that need to be ran in the event queue (see diagram below). Chaining .then on your async code is equivalent to defining a callback. If the blocking operation to $.get completed successfully, it would have put your callback in the event queue with the result of errorTest() to be executed.
If, however, it didn't run successfully (it threw an exception), that exception would bubble up, as all exceptions do until they're caught. If you have defined a .catch, that would be a callback to handle the exception and that'll get placed on the event queue to run. If you did not, the exception bubbles up to the event loop itself and results in the error you saw (Uncaught (in promise) error) -- because the exception was never caught.
Remember, your try/catch has long since finished executing and that function doesn't exist anymore as far as the runtime is concerned, so it can't help you handle that exception.
Now if you add an await before errorTest() the runtime doesn't execute any of your other code until $.get completes. In that case your function is still around to catch the exception, which is why it works. But you can only call await in functions themselves that are prefixed with async, which is what the other commenters are indicating.
Diagram is from https://www.educative.io/answers/what-is-an-event-loop-in-javascript. Recommend you check it out as well as https://www.digitalocean.com/community/tutorials/understanding-the-event-loop-callbacks-promises-and-async-await-in-javascript to improve your understanding of these concepts.
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 )
I am refactoring my promise chaining codes to async/await style. One of the reasons to do that is I want a single catch block to handle to all error situations (as explained here Understanding promise rejection in node.js)
My question is when I hit a sync error, should I call await Promise.reject or throw error to bail out the process?
I know either way will work but I prefer throw error. I already know I got an invalid result why should I await? Using throw to terminate the control flow immediately seems to be a better option.
I am not talking about the promise chain(the whole point of my question), so I don't think the thread JavaScript Promises - reject vs. throw answered my question.
I read the article Error Handling in Node.js I don't think it gives an answer either. But it did say
A given function should deliver operational errors either
synchronously (with throw) or asynchronously (with a callback or event
emitter), but not both. ... In general, using throw and expecting
a caller to use try/catch is pretty rare...
My async function(s) may return Promise.reject. So I am concerned about introducing 2 ways to delivering errors as that article against.
try {
let result = await aysncFunc().
if (!isResultValid(result)) { //isResultValid() is sync function
await Promise.reject('invalid result')
//or throw 'invalid result'
}
... //further processing
}
catch (error) {
...
}
It's semantically correct to use throw in promise control flow, this is generally preferable way to bail out of promise chain.
Depending on coding style, await Promise.reject(...) may be used to differentiate between real errors and expected rejections. Rejected promise with string reason is valid but throw 'invalid result' is considered style problem that may be addressed with linter rules, because it's conventional to use Error instances as exceptions.
The reason why it's important is because string exceptions can't be detected with instanceof Error and don't have message property, consistent error logging as console.warn(error.message) will result in obscure undefined entries.
// ok
class Success extends Error {}
try {
throw new Success('just a friendly notification');
} catch (err) {
if (!(err instanceof Success)) {
console.warn(err.message);
throw err;
}
}
// more or less
const SUCCESS = 'just a friendly notification';
try {
await Promise.reject(SUCCESS);
} catch (err) {
if (err !== SUCCESS)) {
console.warn(err.message);
throw err;
}
}
// not ok
try {
throw 'exception';
} catch (err) {
if (typeof err === 'string') {
console.warn(err);
} else {
console.warn(err.message);
}
throw err;
}
Since invalid result is actually an error, it's reasonable to make it one:
throw new TypeError('invalid result');
I am not talking about the promise chain(the whole point of my question), so I don't think the thread JavaScript Promises - reject vs. throw answered my question.
async function is syntactic sugar for promise chain, so all points that are applicable to promises are applicable to async as well.
There may be cases when throwing an error is not the same as rejecting a promise, but they are specific to other promise implementations like AngularJS $q and don't affect ES6 promises. Synchronous error in Promise constructor results in exception, this also isn't applicable to async.