Home/Learn/JavaScript & TypeScript/Errors — Throwing, Catching and Custom Types

Errors — Throwing, Catching and Custom Types

Intermediate
Async

Throw Error objects, not strings. Catch where you can act, not everywhere. And know the two ways an async error escapes a try/catch.

Overview

Error handling is where sloppy async code becomes unmaintainable. JavaScript lets you throw anything, but throwing a string loses the stack trace, so always throw an Error or a subclass. Custom error classes let callers distinguish a validation failure from a network failure without parsing message strings. The two async-specific traps are a missing await, which turns a catchable throw into an unhandled rejection, and a throw inside a callback such as setTimeout, which no surrounding try/catch can reach.

Custom Error Types

A class per failure mode lets callers branch on type rather than on message text.

Branch on type, not on message
class ApiError extends Error {
  constructor(message, status, body) {
    super(message)
    this.name = 'ApiError'
    this.status = status
    this.body = body
  }
}
class ValidationError extends Error {
  constructor(field, message) {
    super(message)
    this.name = 'ValidationError'
    this.field = field
  }
}

try {
  await submit(form)
} catch (err) {
  if (err instanceof ValidationError) highlight(err.field)
  else if (err instanceof ApiError && err.status === 401) redirectToLogin()
  else report(err)
}

// Preserving the original when re-throwing:
throw new ApiError('Checkout failed', 502, body, { cause: err })

Where try/catch Does Not Reach

Two cases surprise people, and both produce an uncaught error despite a try/catch being right there.

Two errors a try/catch cannot see
// 1. Missing await — the rejection escapes the block
try {
  fetchUser()          // no await
} catch (e) {
  // never runs; becomes an unhandled rejection
}

// 2. Throwing inside a callback that runs later
try {
  setTimeout(() => { throw new Error('boom') }, 100)
} catch (e) {
  // never runs — the stack is gone by the time it throws
}

// The safety nets, for logging rather than recovery:
window.addEventListener('unhandledrejection', e => report(e.reason))
window.addEventListener('error', e => report(e.error))

Catching at the Right Level

Catch where you can do something about it. A catch that logs and continues with bad state is worse than no catch.

Swallowing vs propagating, and retry
// Bad — swallows the failure, caller thinks it worked
async function load() {
  try { return await fetchData() }
  catch { return [] }          // an empty list and an outage look identical
}

// Better — let it propagate to the level that can show an error state
async function load() {
  return fetchData()
}

// And handle where the UI is:
try {
  setItems(await load())
} catch (err) {
  setError(err.message)        // the user finds out
}

// Retry only what is worth retrying
async function withRetry(fn, attempts = 3) {
  for (let i = 0; i < attempts; i++) {
    try { return await fn() }
    catch (err) {
      if (err.status && err.status < 500) throw err    // 4xx will not fix itself
      if (i === attempts - 1) throw err
      await new Promise(r => setTimeout(r, 2 ** i * 200))
    }
  }
}

Key Points to Remember

  • 1Throw Error objects, never strings — a string has no stack trace
  • 2Custom Error subclasses let callers branch on instanceof instead of parsing message text
  • 3A missing await turns a catchable rejection into an unhandled one, even inside try/catch
  • 4A throw inside setTimeout or another deferred callback cannot be caught by the surrounding try/catch
  • 5Catch where you can act; returning an empty list on failure makes an outage indistinguishable from no data

Interview Questions

Sign in to ask Aria
1

Why should you throw an Error object rather than a string?

Easy
2

Give two situations where a try/catch will not catch an error that occurs inside it.

Hard
3

How would you implement retry with exponential backoff, and which errors should not be retried?

Medium

Ask Aria about Errors — Throwing, Catching and Custom Types

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…