Skip to main content

JavaScript Errors

JavaScript Error Handling Patterns

Written by Published

Good error handling is about deciding where a failure should be dealt with.

Wrapping everything in try catch is not error handling.

The useful question is who can actually do something about it.

Example

Example

javascript

function parseUser(text) {
  try {
    return JSON.parse(text);
  } catch (error) {
    return null;
  }
}

console.log(parseUser("not json"));
console.log(parseUser('{"name":"Ada"}').name);

The caller gets null rather than a crash.

Where to Handle

Catch where you can genuinely recover.

Let it travel up when the caller knows better than you do.

Handle it at the boundary: a button click, a request, a page load.

Syntax

Syntax

javascript

if (!valid) return fallback;

A guard clause often removes the need to throw at all.

Fail Fast

Check input at the top of a function and stop immediately.

The error then points at the real cause, not three functions later.

Example

Example

javascript

function area(width, height) {
  if (typeof width !== "number" || typeof height !== "number") {
    throw new Error("width and height must be numbers");
  }
  return width * height;
}

let message;
try {
  area("4", 5);
} catch (error) {
  message = error.message;
}

console.log(message);

Without the check this would silently return 20.

Guard Clauses

Often a simple check is better than throwing at all.

Example

Example

javascript

function greet(name) {
  if (!name) {
    return "Hello stranger";
  }
  return "Hello " + name;
}

console.log(greet(""));
console.log(greet("Ada"));

Missing input is expected here, so it is not exceptional.

Do Not Catch What You Cannot Fix

Catching and ignoring turns a loud bug into a silent one.

If you cannot recover, let it travel up.

Example

Example

javascript

function risky() {
  null.length;
}

function middle() {
  return risky();
}

let name;
try {
  middle();
} catch (error) {
  name = error.name;
}

console.log(name);

The middle function stayed out of the way, which is correct.

Adding Context When Rethrowing

If you rethrow, say something the original error did not.

Example

Example

javascript

function loadSettings(text) {
  try {
    return JSON.parse(text);
  } catch (error) {
    throw new Error("settings file is not valid JSON");
  }
}

let message;
try {
  loadSettings("nope");
} catch (error) {
  message = error.message;
}

console.log(message);

The new message says which file, which the original never knew.

Handling at the Boundary

One handler at the top is usually better than many scattered ones.

That is where you can show the user something useful.

Example

Example

javascript

function step(n) {
  if (n === 2) throw new Error("step two failed");
  return "step " + n;
}

function runAll() {
  const done = [];
  for (const n of [1, 2, 3]) {
    done.push(step(n));
  }
  return done;
}

let message;
try {
  runAll();
} catch (error) {
  message = "stopped because " + error.message;
}

console.log(message);

The loop did not need its own handler.

Complete Example

Complete Example

html

<!DOCTYPE html>
<html>
<head>
  <title>JavaScript Error Handling Patterns</title>
</head>
<body>

  <h1>Error Handling Patterns</h1>

  <input id="text" value="not json">
  <button id="load">Load</button>

  <p id="out"></p>

  <script>
    function loadSettings(text) {
      try {
        return JSON.parse(text);
      } catch (error) {
        throw new Error("settings are not valid JSON");
      }
    }

    document.getElementById("load").addEventListener("click", function () {
      const out = document.getElementById("out");
      try {
        const settings = loadSettings(document.getElementById("text").value);
        out.textContent = "Loaded " + Object.keys(settings).length + " settings";
      } catch (error) {
        out.textContent = "Could not load: " + error.message;
      }
    });
  </script>

</body>
</html>

Try It Yourself

Run the above example in the Try It Editor.

Try valid JSON:

Enter {"theme":"dark"} and click Load.

Important Points

  • Catch only where you can recover.
  • Check input at the top and fail fast.
  • A guard clause is often better than throwing.
  • Never catch and ignore.
  • Handle errors at the boundary of your program.

Conclusion

Error handling is a design decision, not a wrapper you add everywhere.

The question is always who can do something useful about the failure.

A single handler at the boundary usually beats many scattered ones.