8

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.

happy_story
  • 1
  • 1
  • 7
  • 17
  • 1
    You need `await errorTest()` in order to get an error in the `catch` block. Otherwise you need to attach a `.catch()` handler. – VLAZ Jun 23 '21 at 15:04
  • `await errorTest()` doesn't work. It gives me ` await is only valid in async functions and the top level bodies of modules` error. `catch()` handler works, but I don't understand why. I need someone to explain this to me in a more detailed manner. I thought using `async` returns a promise, so why is `catch()` outside not catching it? – happy_story Jun 23 '21 at 15:27
  • @Snirka `then()` doesn't return a promise, it resolves a promise. The rest of your comment do not address my question at all. – happy_story Jun 23 '21 at 15:29
  • @happy_story "*then() doesn't return a promise, it resolves a promise.*" and then returns a new promise with the result... – VLAZ Jun 23 '21 at 15:49
  • "*I thought using async returns a promise, so why is catch() outside not catching it?*" because `try`/`catch` does not catch problems in un-`await`ed promises. Any un-`await`ed promise will just resolve later and the code that produces one will not wait for it to resolve. Which is why `try`/`catch` doesn't work - you cannot `catch` something that will happen in the future. – VLAZ Jun 23 '21 at 15:51
  • So, you mean to say that, `catch()` doesn't capture the error, because the promise itself was not captured, because `try {}` doesn't capture a promise, whereas `.then()` does? But is there a type of error? In this case, the error was caused by a broken link, so is this like a promise error, and to capture it, the promise must be captured first? What if I do a different kind of error, would it work then? – happy_story Jun 23 '21 at 16:02
  • 2
    Look, if you execute a function that returns a promise *and you don't `await` it*, then that will resolve later. And any handling will also be later. So `one(); try { asyncFn() } catch() {} two();` will call `one`, then `asyncFn` then *not* wait, and call `two`. Whatever happens with the promise will happen in the future after this code finishes executing ``one(); try { await asyncFn() } catch() {} two();` will instead wait for the promise from `asyncFn` to resolve before calling `two`. And if `asyncFn` results in rejection, it will go in the catch block. – VLAZ Jun 23 '21 at 16:09
  • 1
    There is nothing special about `try`/`catch` about handling promises. It's the `await` with a rejected promise that will throws the rejection and thus trigger the `catch` block. – VLAZ Jun 23 '21 at 16:09
  • I don't understand your example with `one()` and `two()`. In my example, there is only one function, not 3. Either way, like I said above, I DID try `await` in front of `errorTest()` inside `try {}`, and I got an error ` await is only valid in async functions`. So, why is it not working? – happy_story Jun 23 '21 at 16:38
  • Note that, in my example, as shown above, `try and catch` are OUTSIDE the function, not inside. Did you by any chance thought that they were inside? – happy_story Jun 23 '21 at 16:40

3 Answers3

9
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"))
Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
  • So, is the problem the fact that, `try{}` does not capture a promise, and therefore does not pass along a promise error to `catch()`, or the fact that, because `try{}` is not synced, JS executes it but doesn't wait for the return value, instead executes `catch()` right away, which results in `catch()` not receiving the error thrown at it by `try{}`? I tried the same example but with a primitive value, `const result = a - c; return result`, `c` was undefined, and it worked, `catch()` caught the error. So, is the first explanation the correct one? – happy_story Jun 23 '21 at 18:19
  • Also, sorry to say but I didn't quite understood your examples. In your first example, you're returning a new promise, so.. if there's an error inside, how is nothing not going to be thrown? In your second example, you are just defining a new Error.. how is that relevant to the question? You emphasize a lot the synchronous vs asynchronous aspect of the code, but I've kind of built a mental image of capturing a promise vs not capturing, resolving vs not resolving .. and the emphasis on async vs sync doesn't really resonate that much with me. I hope that makes sense. – happy_story Jun 23 '21 at 18:22
  • My whole purpose was to have two `catch()` in order to demonstrate that if there's an error, the `catch()` closest to the func that produced the error will capture it, but I struggle with coming up with a case like that. – happy_story Jun 23 '21 at 18:23
  • This is a great answer from Jeff. @happy_story In order for your `try` `catch` to work is stated above, you need to append the `await` keyword before the function call, as behind the scenes chains a `.catch()` on the result failure if occurred. – John Jan 07 '22 at 11:48
2

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)
}
}

  • The example that you're giving me is very different from my example. I don't wanna put try and catch inside the function. If you're going to answer my question, do it in the way I've asked it. Don't alter the question. – happy_story Jun 23 '21 at 15:40
  • From your code, you are not awaiting "erroTest()" in the try catch block,that's why you are having "Uncaught (in promise) error". And the try catch block has to be in an async function, because you have to await "errorTest()", for it to work. – Ayotunde Ajayi Jun 23 '21 at 16:17
  • So, you mean to say that, `catch()` doesn't capture the error, because the promise itself was not captured, because `try {}` doesn't capture a promise, whereas `.then()` does? But is there a type of error? In this case, the error was caused by a broken link, so is this like a promise error, and to capture it, the promise must be captured first? What if I do a different kind of error, would it work then? And is there a way to capture the promise outside the function in my example? – happy_story Jun 23 '21 at 16:42
  • Yes.Because `errorTest` is async, it will return a promise and will only execute when you await it an a async function. That's why you have `"Uncaught (in promise) error"`. If you console the `errorTest()` function in the try catch block of your example, you will have `""`, meaning you are not awaiting it. If you intentionally make an error, it would work. You can capture the promise outside the `errorTest()` function by putting the try catch block in another async function and call the function, as in my example. If they is an error, it will enter the catch block – Ayotunde Ajayi Jun 23 '21 at 17:29
  • I was trying to create an example in which I have two `catch()`, in order to demonstrate that if there's an error thrown, the first `catch()` closest to the function will capture it, but I can't come up with such an example anymore. Is such an example even possible? – happy_story Jun 23 '21 at 18:24
  • Can you be more more clearer with the question, I can't seem to understand the question. Do you mean having a `try/catch` in another `try/catch` block – Ayotunde Ajayi Jun 23 '21 at 19:00
  • The description of `throw` in the `https://developer.mozilla.org` is that, quote `The throw statement throws a user-defined exception. Execution of the current function will stop (the statements after throw won't be executed), and control will be passed to the first catch block in the call stack. If no catch block exists among caller functions, the program will terminate.` So, the way I understood this is that, the error will be passed to the FIRST `catch()` so.. therefore, there can be more than 1, and if there are multiple `catch()`, the first one will capture the error. – happy_story Jun 23 '21 at 20:31
  • So, I wanted to test this by having multiple `catch()` in order to see how the first one will capture the error. Is my understanding of this description wrong? – happy_story Jun 23 '21 at 20:31
  • I know it can work like this. `try{ try{ throw new error('my error')} catch (err) {throw new error(error)} }catch(err){console.log(error)}` – Ayotunde Ajayi Jun 24 '21 at 06:04
  • I'm really not sure if this is what they meant, because your code is outputting both errors, whereas the way I understood is it that, if there are two `catch() blocks`, the first one will capture it. In your code, both errors are captured. – happy_story Jun 24 '21 at 11:18
  • Can I ask you one more thing? Why is it that, when I throw an error inside `catch()`, it gives me `uncaught error` error message? In this code: https://jsfiddle.net/brcpj5kw/ – happy_story Jun 24 '21 at 11:20
  • From the code in the link, you threw the error in the `catch()` block, so it will need to be caught in another `try/catch()` block – Ayotunde Ajayi Jun 24 '21 at 17:53
  • `try{ try{ throw new error('my error')} catch (err) { console.log eror} }catch(err){ }` . In this one, only the first `catch()` captures the error because I did not throw it again in the first `catch()` as in the first example. So in this one, the second `catch()` block won't get the error, if I understand you correctly – Ayotunde Ajayi Jun 24 '21 at 17:57
  • Can you explain to me in this code : https://jsfiddle.net/m9nkLwqh/ .. why is the error that is thrown from the inner catch() caught OUTSIDE, in the outside try {} and catch()? Since, the outside try {} did not caught any errors with the execution of the function statements, right? The error was inside the inner try {}, so it should be send to the inner catch(), right? Why is it being send to the outer catch()? Who sends it to the outer catch()? I don't understand why does the outer catch() gets the error since the outer try {} did not received any error. – happy_story Jun 24 '21 at 18:24
  • 1
    You called the `errorTest()` function inside the second `try/catch` block, although in a console.log. So the error is caught because you threw the error in the first `catch()`, so, when you did that, and you called the function in the second `try/catch` block, it entered the second `catch`, but if you didn't throw the error in the first, or `console.log` it in the first `catch`, it would not have been caught in the second `catch` – Ayotunde Ajayi Jun 24 '21 at 19:12
1

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.

enter image description here

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.

Kyle Chadha
  • 3,741
  • 2
  • 33
  • 42