Reputation: 1155
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
.
Upvotes: 11
Views: 28835
Reputation: 349
// async means immediately return a new Promise object and access the
// returned value later using `await` or `.then()`
const errorTest = async () => {
const result = await $.get("http://dataa.fixer.io/api/latest?access_key=9790286e305d82fbde77cc1948cf847c&format=1");
return result;
}
try {
// create a Promise but do nothing with it ("fire and forget")
errorTest();
// try block completes synchronously, whereas using `await`
// would have allowed it to be caught
}
catch(err) {
// this will NEVER be executed, because the try block will always
// succeed in creating a new Promise
// Promise objects MUST be awaited in the try block in order to make
// use of the catch block
console.log("OUTSIDE ERROR!" + err)
}
// the try block completes synchronously,
// but there's still a Promise at this point.
// the Promise resolves here, at which point the error is reported as Uncaught
// Note: if you cannot await the Promise in the try block above, it
// is either not placed in an asynchronous function, or your JavaScript
// environment (does not support/has not enabled) top-level await
Upvotes: 0
Reputation: 4151
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.
Upvotes: 2
Reputation: 95614
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"))
Upvotes: 11
Reputation: 79
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)
}
}
Upvotes: 3