Javascript Try Catch vs Catch chain - javascript

I recently ran into a Javascript problem catching errors and thus crashing when exception thrown.
funcReturnPromise().then().catch()
I had to change this to:
try {
funcReturnPromise().then()
} catch (e) {
...
}
Couldn't find a decent explanation for it, any JS wizards available to enlighten a JS peasant?

If funcReturnPromise() can throw synchronously (which functions that return promises should generally never do), then you do have to catch that synchronous exception with try/catch as you discovered when using regular .then().
This is one place where async functions can hep you. For example, if you declare funcReturnPromise as async, then the synchronous exception it throws will automatically become a rejected promise and the caller won't ever be exposed to a synchronous exception.
Or, if the caller (your code here) uses await, then you can catch both sycnhronous exceptions and rejected promises with the same try/catch.
So, for example, you could do this:
async function myFunc()
try {
let result = await funcReturnPromise();
console.log(result);
} catch (e) {
// this catches both a rejected promise AND
// a synchronously thrown exception
console.log(e);
}
}

Related

Why is try {} .. catch() not working with async/await function?

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.

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.

try/catch issues with nested functons in javascript

I'm having a scenario like below:
try {
top();
} catch(e) {
console.log(e);
}
function top() {
nestedFunc1();
}
function nestedFunc1() {
nestedFunc2();
}
function nestedFunc2() {
// this function throws an exception.
}
my catch block doesn't get executed whenever an exception is thrown in my node script. Is this behaviour expected or i'm missing something here.
With the code above, an exception from nestedFunc2 will definitely be caught by that try/catch block.
My suspicion is that what you really have is an exception in an asynchronous callback:
try {
someNodeAPIFunction((err, result) => {
// Exception thrown here
});
} catch (e) {
console.log(e);
}
If so, then yes, it's perfectly normal that the try/catch there doesn't catch the exception, because that code has already finished running. The callback is called later. The only thing that can catch the exception is the code in someNodeAPIFunction that calls the callback.
This is one of the reasons callback APIs are awkward to work with.
In any vaguely-recent version of Node.js, you can use async/await to simplify things. Node.js provides Promise-enabled versions of some of its API functions now (in particular the fs.promises module) and also provides the promisify utility function that you can use to turn a standard Node.js callback-style function into one that returns a promise. So for instance, with the above:
const someNodeAPIFunctionPromisified = util.promisify(someNodeAPIFunction);
Then, in an async function, you could do this:
try {
const result = await someNodeAPIFunctionPromisified();
// Exception thrown here
} catch (e) {
console.log(e);
}
...and the exception would be caught, because async functions let you right the logical flow of your code even when dealing with asynchronous results.
More about async functions on MDN.

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 )

Catching errors with Q.deferred

I have a nodeJS project where I wish to use asynchronous functions. Namely, I'm using the Q library.
I have a function called someFunction(), that I wish to return a promise. With the then -function, I can then check whether the promise was resolved or rejected like so:
someFunction()
.then(
function(results) {
console.log("Success!");
},
function (error) {
console.log("An error occurred, and I would wish to log it for example");
}
);
What I intuitively expect with the function above is, that the error function would catch all possible errors. So if some exception is raised inside the someFunction, I can rest assured, that the error function will be run (the second function after 'then'). But it seems this is not the case.
For example, let's say the someFunction would be defined as so:
function someFunction() {
var deferred = Q.defer();
throw new Error("Can't bar.");
deferred.resolve("success");
}
Now if I call the function someFunction() like done in the upper code block, it won't run the error function. Why is that? Isn't the partial point of the promise Q.deferred to catch errors? Why should I manually reject every error that happens? I know I could set the whole content of someFunction to try/catch clause, and then reject the deferred, but that feels so wrong! There must be a better way, and I know for sure some of you stackoverflowers know it!
With this information, I began to think about where the deferred.reject and deferred.resolve is even meant to be used? Is it even meant to catch exceptions? Should I really just go through all the error cases manually, and call the deferred.reject on them? I'm interested to hear how this should be handled professionally.
Q has specific function for success and error, so use:
deferred.reject("error");
intead of throwing an Exception.
Next thing is that someFunction must return promise to be used as You use it:
function someFunction() {
var deferred = Q.defer();
try{
//some Asynchronous code
deferred.resolve("success");
}catch(e){
deferred.reject(e.message);
}
return deffered.promise; //return promise to use then
}
Because Promises ain't magic. They don't somehow magically catch Errors. They catch them, because they wrap the calls to the callbacks in try..catch-blocks, to convert Errors into rejected Promises.
If you want an Error to be handled by the Promise-chain, well put the function-call into a promise-chain: Q.resolve().then(someFunction).then(...).
Now any synchronous Error occuring in someFunction can be handled in the following then's.
Btw: If you use Q.defer(), and you're not dealing with some callback-style API, you're doing it definitely wrong. Search for the Deferred-antipattern.
it won't run the error function. Why is that?
Because you synchronously threw an exception, instead of returning a promise. Which you never should.
Isn't the partial point of the promise Q.deferred to catch errors?
No. then and catch implicitly catch exceptions in their callbacks, defferreds don't - they're just a (deprecated) API to create promises.
Why should I manually reject every error that happens?
Because asynchronous errors are expected to be passed to callbacks anyway, instead of being thrown.
I know I could set the whole content of someFunction to try/catch clause, and then reject the deferred, but that feels so wrong! There must be a better way!
There is: the Q.Promise constructor, the standard (ES6) promise creation API. It has the benefit of being able to catch synchronous exceptions from the executor function:
function someFunction() {
return new Q.Promise(function(resolve) {
throw new Error("Can't bar.");
resolve("success");
});
}
throwing an error will stop the code from executing (and will close node) unless you catch the error in a try/catch block.
handled errors from requests can be passed to the .catch chain by using deferred.reject(error). code errors and custom thrown errors needs to be handled inside try/catch, which is the right way to handle such errors.
function someFunction() {
var deferred = Q.defer();
deferred.reject("Can't bar.");
// or
try {
throw new Error("Can't bar.");
}
catch(err) {
deferred.reject("Can't bar.");
}
deferred.resolve("success");
}

Categories

Resources