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.

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

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*

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

If you found this post useful, please drop a ❤️ or leave a kind comment!

Okay