Code

js/errors.js — typed errors

In the code column — js/errors.js. The base class AppError carries code, cause and retryable; the specific errors extend it. The point isn't the hierarchy itself — it's that the caller can branch on the error type.

Now api.js (from lesson 4) throws typed errors instead of new Error(...):

import { NetworkError, RateLimitError, NotFoundError, ApiError } from './errors.js';

async function request(url) {
  let response;
  try {
    response = await fetch(url);
  } catch (cause) {
    throw new NetworkError(cause);            // only a network failure lands here
  }
  if (response.ok) return response.json();

  if (response.status === 429) throw new RateLimitError();
  if (response.status === 404) throw new NotFoundError('That picture');
  if (response.status === 403) {              // NASA sometimes 403s a spent DEMO_KEY
    const body = await response.text().catch(() => '');
    if (body.toLowerCase().includes('rate limit')) throw new RateLimitError();
  }
  throw new ApiError(response.status, await response.text().catch(() => ''));
}

Now main.js can respond differently: if (error.retryable) offerRetry(), taking the message from toMessage(error).

Tip (where's "Sentry"?). The real errors.js also has reportError() and installGlobalHandlers() — one place where errors are sent to a monitoring tool (Sentry, Datadog) and where anything nobody else caught is caught (window.onerror, unhandledrejection). An app should never die silently. We'll add those parts later; for now, the point is typed errors.