DEV Community

Cover image for How JavaScript Lost Its Way With Error Handling (Can We Fix It?)
Mat Silva
Mat Silva

Posted on

1 1 1

How JavaScript Lost Its Way With Error Handling (Can We Fix It?)

JavaScript Errors Used to Be Simple

We had a dedicated channel in callbacks. We knew when something went wrong for that function.

fs.readFile('file.txt', (err, data) => {
  if (err) {
    console.error(err);
    return;
  }
  console.log(data);
});
Enter fullscreen mode Exit fullscreen mode

You checked for the error. You dealt with it.

No surprises. No magic.

It wasn’t pretty, because callback hell. But it was clear.

Then Came Async/Await

It looked clean. Linear. Easy to follow. Arguably, it still is.

But we started throwing errors again. All in the same channel.

Like this:

fastify.get('/user/:id', async (req, reply) => {
  const user = await getUser(req.params.id);
  if (!user) throw fastify.httpErrors.notFound();
  return user;
});
Enter fullscreen mode Exit fullscreen mode

This seems fine—until you need to do more than one thing.

Suddenly, your catch block becomes a patchwork of if-statements:

fastify.get('/user/:id', async (req, reply) => {
  try {
    const user = await getUser(req.params.id);
    if (!user) throw fastify.httpErrors.notFound();

    const data = await getUserData(user);
    return data;
  } catch (err) {
    if (err.statusCode === 404) {
      req.log.warn(`User not found: ${req.params.id}`);
      return reply.code(404).send({ message: 'User not found' });
    }

    if (err.statusCode === 401) {
      req.log.warn(`Unauthorized access`);
      return reply.code(401).send({ message: 'Unauthorized' });
    }

    req.log.error(err);
    return reply.code(500).send({ message: 'Unexpected error' });
  }
});
Enter fullscreen mode Exit fullscreen mode

You're using catch not just for exceptions, but for expected things:

  • A user not found
  • Invalid auth
  • Bad input

You're forced to reverse-engineer intent from the thrown error.

You lose clarity. You lose control.

Other Languages Seem To Do Better

Go

Go keeps it simple. Errors are values.

data, err := ioutil.ReadFile("file.txt")
if err != nil {
    log.Fatal(err)
}
Enter fullscreen mode Exit fullscreen mode

You deal with the error. Or you don’t. But you don’t ignore it.

Scala

Scala uses types to make the rules clear.

val result: Either[Throwable, String] = Try {
  Files.readString(Path.of("file.txt"))
}.toEither

result match {
  case Left(err)   => println(s"Error: $err")
  case Right(data) => println(s"Success: $data")
}
Enter fullscreen mode Exit fullscreen mode

You must handle both outcomes.

No free passes. No silent failures.

Use Option for missing values.

val maybeValue: Option[String] = Some("Hello")

val result = maybeValue.getOrElse("Default")
Enter fullscreen mode Exit fullscreen mode

No null. No undefined. No guessing.

What JavaScript Could Be

We don’t have to do this:

try {
  const data = await fs.promises.readFile('file.txt');
} catch (err) {
  console.error(err);
}
Enter fullscreen mode Exit fullscreen mode

We could do this:

const [err, data] = await to(fs.promises.readFile('file.txt'));

if (err) {
  console.error('Failed to read file:', err);
  return;
}

console.log('File contents:', data);
Enter fullscreen mode Exit fullscreen mode

It’s clear. It’s honest. It works.

Or we use a result wrapper:

const result = await Result.try(() => fs.promises.readFile('file.txt'));

if (result.isErr) {
  console.error(result.error);
} else {
  console.log(result.value);
}
Enter fullscreen mode Exit fullscreen mode

You know what's expected. You know what blew up.

Want to Write Better Code?

Here are some tools to help with that:

One Last Thing

This is a bit of the old “you made your bed, now lie in it.”
We started throwing everything into a single channel.
We didn’t think it through.

But it’s fixable.

Choose better patterns.
Throw less.
Write what you mean.

Heroku

Built for developers, by developers.

Whether you're building a simple prototype or a business-critical product, Heroku's fully-managed platform gives you the simplest path to delivering apps quickly — using the tools and languages you already love!

Learn More

Top comments (2)

Collapse
 
shadowshahriar profile image
S. Shahriar

Your post came right in time as I was preparing to rewrite some of the old code of a JavaScript project. It will really help me to have more control over the errors my project throws at me 🙌🏼

Collapse
 
matsilva profile image
Mat Silva • Edited

Good luck, I'm sure it may be a challenge to unwind it all*

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

👋 Kindness is contagious

Engage with a wealth of insights in this thoughtful article, valued within the supportive DEV Community. Coders of every background are welcome to join in and add to our collective wisdom.

A sincere "thank you" often brightens someone’s day. Share your gratitude in the comments below!

On DEV, the act of sharing knowledge eases our journey and fortifies our community ties. Found value in this? A quick thank you to the author can make a significant impact.

Okay