# Error Handling

> Source: https://learn-javascript.org/error-handling/
> Part of Learn JavaScript, free to read.

```javascript
try {
  const data = JSON.parse(input);
  process(data);
} catch (err) {
  console.error("could not parse:", err.message);
} finally {
  cleanup();          // runs whether or not it threw
}
```

## The Error object

```javascript
throw new Error("something broke");

const err = new Error("boom");
err.name;        // "Error"
err.message;     // "boom"
err.stack;       // where it was created
```

Built-in subclasses carry meaning:

```javascript
TypeError        // wrong type — null.foo, undefined is not a function
RangeError       // out of range — new Array(-1)
SyntaxError      // bad syntax — JSON.parse("{")
ReferenceError   // undeclared variable
```

**Always throw an `Error`, never a string.** A thrown string has no stack trace, and every `catch` block that does `err.message` gets `undefined`.

```javascript
throw "not found";              // no stack, no name, breaks every handler
throw new Error("not found");   // correct
```

## Custom error classes

```javascript
class NotFoundError extends Error {
  constructor(resource, id) {
    super(`${resource} ${id} not found`);
    this.name = "NotFoundError";
    this.resource = resource;
    this.id = id;
  }
}

class ValidationError extends Error {
  constructor(field, reason) {
    super(`${field}: ${reason}`);
    this.name = "ValidationError";
    this.field = field;
  }
}
```

Now callers can distinguish failures and respond appropriately:

```javascript
try {
  await createOrder(payload);
} catch (err) {
  if (err instanceof ValidationError) return respond(400, { field: err.field });
  if (err instanceof NotFoundError)   return respond(404);
  throw err;                          // not ours — let it propagate
}
```

That final `throw err` matters. Catching an error you cannot handle and swallowing it is how bugs become invisible.

## Preserving the cause

```javascript
try {
  await db.query(sql);
} catch (err) {
  throw new Error(`loading user ${id} failed`, { cause: err });
}
```

The `cause` option keeps the original error attached, so you get context *and* the underlying failure. Without it you either lose the detail or lose the context.

```javascript
catch (err) {
  console.error(err.message);        // "loading user 1 failed"
  console.error(err.cause.message);  // "connection refused"
}
```

## Async errors

```javascript
try {
  const user = await getUser(id);
} catch (err) {
  // works — a rejected promise throws at the await
}
```

But only if you `await`. Without it, the error escapes:

```javascript
try {
  getUser(id);            // not awaited
} catch (err) {
  // never runs. the rejection happens after this block.
}
```

:::danger An unhandled rejection terminates the process
Since Node 15 the default for an unhandled promise rejection is to crash. A single un-awaited call in a background job can take down your whole server.

```javascript
process.on("unhandledRejection", (reason) => {
  logger.fatal({ reason }, "unhandled rejection");
  process.exit(1);
});
```
Log it and exit deliberately rather than letting it crash silently — but treat every occurrence as a bug to fix, not a condition to handle. The real fix is `no-floating-promises` in your linter.
:::

## Errors in callbacks

```javascript
try {
  setTimeout(() => { throw new Error("boom"); }, 0);
} catch (err) {
  // never runs
}
```

The callback runs later, on a fresh stack. The `try` block has long since finished. Anything asynchronous needs its error handling *inside* the callback, or needs to be promise-based so `await` can catch it.

## What not to do

**Swallowing:**

```javascript
try { risky(); } catch (err) {}                    // the incident starts here
try { risky(); } catch (err) { console.log(err); } // barely better
```

If you cannot handle it, do not catch it. If you catch it deliberately — a cache read that is allowed to fail — say so in a comment.

**Catching too broadly:**

```javascript
try {
  const data = JSON.parse(input);
  await save(data);              // a save failure now looks like a parse failure
} catch (err) {
  return { error: "invalid JSON" };
}
```

Keep the `try` around the operation you are actually handling.

**Returning error codes instead of throwing:**

```javascript
const result = doThing();
if (result === -1) { }          // easy to ignore; no stack; no message
```

Throw, or return a discriminated result object (`{ ok: true, value }` / `{ ok: false, error }`) — but pick one convention per codebase.

## Result objects

For expected failures, a returned result is often better than an exception, because the type system and the reader can both see it:

```javascript
async function findUser(id) {
  const row = await db.get(id);
  return row ? { ok: true, value: row } : { ok: false, error: "not_found" };
}

const result = await findUser(id);
if (!result.ok) return respond(404);
use(result.value);
```

Reserve exceptions for the genuinely exceptional — a database being down, a bug — and use results for outcomes you expect, like "not found" or "invalid input".

## Exercise

```javascript
// 1. Write a ValidationError class carrying a `field`.
// 2. Write validateOrder(order) that throws it for a missing customerId
//    or an empty items array.
// 3. Write handleRequest(order) that catches ValidationError and returns
//    { status: 400, field }, re-throws anything else, and wraps a database
//    failure with `cause` preserved.
console.log("start");
```

## Common questions

### Should I throw or return an error?

Throw for the unexpected — a bug, a dependency that is down, a broken invariant. Return a result for expected outcomes like "not found" or "invalid input", where the caller must handle it anyway and an exception is control flow in disguise.

### Why is my `catch` not catching?

The three usual causes: the promise was not awaited, the error was thrown inside a later callback, or something between you and the throw already caught and swallowed it. The first is by far the most common.

### Is it ever fine to catch and ignore?

Occasionally, and it should look deliberate: a cache read that may fail, a best-effort cleanup. Write the comment saying why, so the next reader knows it is a decision rather than an oversight.
