With async/await becoming standard procedure for a lot of my code, I find myself wrapping blocks of code in try/catch to ensure I'm handling errors properly.

As a result of this, I also try to make my errors a little more useful at the same that I want to show you in this mini post.

The example

I have large validation process that runs through a series of statements and checks for particular problems and rules.

Each validation rule is run and if it fails, it throws with a message, such as:

export function validateFakeExample(token, scope) {
  if (token.text !== 'PRINT') return;

  const next = scope.peekNext();
  if (next.name !== AT) {
    throw new Error('Parser error, PRINT keyword should be followed by AT');
  }
}

My main function runs each of the validation rules all encapsulated inside of a try/catch because I also want to capture additional metadata that will help my user understand what caused the error.

So in my wrapping catch I have something like this:

} catch (error) {
  const message = error.message + `, "${token.text}" at: ${token.pos + 1}`;
  throw new Error(message);
}

This way, when the error is given back to my user, they'll see:

Parser error, PRINT keyword should be followed by AT, "INK" at: 10

Actually I use this pattern a lot, to catch the source error, interpret it, and throw new Error to help me better understand what was going on.

Except it can be smarter.

Being smart

When I call new Error a brand new error object is created. At this point a few things happen, specifically the stack is captured. A stacktrace is incredibly useful for debugging to trace back to the source of the problem.

Except, because I generated a brand new error, my stacktrace will originate from within the catch, which is helpful to a certain degree, but could be a lot more useful.

I could do something with the stack. At times I've added a console.log(error.stack) in the catch which I can go searching my logs for.

What I should do is instead of throwing a new Error, I can simply modify the original error.message property (🤦 why did it take me that long).

So now my code looks like this:

} catch (error) {
  error.message += `, "${token.text}" at: ${token.pos + 1}`;
  throw error;
}

My custom error message is passed by to the user (normally me) and my full stacktrace is retained.