JSTAcademy
0 XP
Dashboard
Crash Courses
Error Handling & Defensive Patterns
11 min
Masters+175 XP
Crash Courses · Masters

Error Handling & Defensive Patterns

Write code that fails gracefully — catching, surfacing, and recovering from errors correctly.
11 min read+175 XP on completionCert: JavaScript Crash Course
Tap any word in the text below to start reading from there.

Error Handling & Defensive Patterns

Production code fails. Networks drop, APIs return unexpected shapes, users provide bad input. Code that cannot handle failure is not production-ready.

try / catch / finally

async function fetchUser(id) {
  try {
    const res = await fetch(`/api/users/${id}`)
    if (!res.ok) throw new Error(`HTTP ${res.status}`)
    return await res.json()
  } catch (err) {
    console.error('fetchUser failed:', err.message)
    throw err  // re-throw unless you can handle it here
  } finally {
    setLoading(false)  // always runs
  }
}

Custom Error Classes

class ApiError extends Error {
  constructor(message, status) {
    super(message)
    this.name = 'ApiError'
    this.status = status
  }
}

class ValidationError extends Error {
  constructor(field, message) {
    super(message)
    this.name = 'ValidationError'
    this.field = field
  }
}

try {
  await api.getUser(id)
} catch (err) {
  if (err instanceof ApiError && err.status === 404) return null
  if (err instanceof ValidationError) {
    showFieldError(err.field, err.message)
    return
  }
  throw err  // unexpected — re-throw
}

Guard Clauses

// BAD — nested
function processOrder(order) {
  if (order) {
    if (order.items.length > 0) {
      if (order.total > 0) { /* logic buried */ }
    }
  }
}

// GOOD — guard clauses
function processOrder(order) {
  if (!order) throw new ValidationError('order', 'Order is required')
  if (order.items.length === 0) throw new ValidationError('items', 'Cart is empty')
  if (order.total <= 0) throw new ValidationError('total', 'Invalid total')

  return chargeAndFulfill(order)
}

Go-Style Error Tuples

async function safeGet(url) {
  try {
    const data = await api.get(url)
    return [data, null]
  } catch (err) {
    return [null, err]
  }
}

const [user, err] = await safeGet('/api/user')
if (err) { handleError(err); return }
// user guaranteed defined here
0%