JavaScript Lost Its Way With Error Handling
Mat Silva

Mat Silva @matsilva

About: Software Engineer | 10+ years building SaaS platforms for startups Building godeploy.app -> ship unlimited static sites or single page apps. 1 command deploys.

Location:
Satellite Beach, FL
Joined:
Feb 26, 2019

JavaScript Lost Its Way With Error Handling

Publish Date: Mar 24
3 2

Reasoning About 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, to a fault.

Now errors are 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.

Comments 2 total

  • S. Shahriar
    S. ShahriarMar 24, 2025

    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 🙌🏼

    • Mat Silva
      Mat SilvaMar 24, 2025

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

Add comment