Caveats

We had a look at simple error handling with and without errors. Let’s have a look now at some special cases.

Returning from an async function

Let’s start with a brain teaser, what will happen with the following code?

At first, we might expect the output to be:

We do cleanup here

Nothing Found

But instead, we got a UnhandledPromiseRejection ! But why? Let’s step through the code:

thisThrows() is an async method

is an method We throw an error in thisThrows()

As thisThrows() is async the thrown error is returned as a rejected promises from the function.

is the thrown error is returned as a rejected promises from the function. We return that rejected promised in myFunctionThatCatches() with the return statement.

with the statement. Our rejected promise reaches the await keyword. The await keyword discovers that the Promise is resolved with the status “rejected” and propagates the error as an unhandled promise rejection.

Based on how our code is structured, our error snuck past our try...catch block and propagated further down in the call tree. Not good!

We solve this by using the await keyword in the return statement.

Now our try..catch works as expected. Now the await keyword on line 7 will first await the returned promise of thisThrows() and if that promise rejects, the error will be propagated to the catch block on line 8 . Problem solved!

Resetting your stack trace

How to handle these use cases will be different on a per-case basis, make sure to be aware of this common mistake and then decide if it’s relevant for you or not.

It’s not uncommon to see code where someone catches an error and wraps it in a new error, like in the following snippet:

Notice that our stack trace only starts where we caught the original exception. When we create an error on line 2 and catch it on 9 , we lose the original stack trace as we now create a new error of type TypeError and only keep the original error message (sometimes we don’t even do that).

Imagine that the thisThrows() function has far more logic in it, and somewhere in that function an error is thrown, we won’t see in our logged stack trace the origin of the problem, as we created a new error which will build a brand new stack trace. If we just re-throw our original error, we won’t have that problem:

Notice that the stack trace now points to the origin of the actual error, being on line 2 from our script.

It is important to be aware of this problem when handling errors. Sometimes this might be desirable, but often this obfuscates the origin of your problem, making it hard to debug the root of a problem. If you create your own custom errors for wrapping errors, make sure you keep track of the original stack trace so that debugging doesn’t turn into a nightmare.