Errors in Production

Unhandled errors crash user flows, leak stack traces, and erode trust. Production JavaScript requires deliberate error handling at every layer — synchronous code, Promises, async/await, event handlers, and third-party scripts.

Error Types

JavaScript provides built-in error constructors:

Type When thrown
Error Generic errors
SyntaxError Invalid syntax (usually parse time)
ReferenceError Undefined variable
TypeError Wrong type operation
RangeError Value out of range
URIError Invalid URI handling
  try {
    JSON.parse('invalid json');
} catch (err) {
    console.log(err instanceof SyntaxError); // true
    console.log(err.message);
    console.log(err.stack);
}
  

Custom Errors

Create domain-specific errors for clearer handling:

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

class NetworkError extends Error {
    constructor(message, statusCode) {
        super(message);
        this.name = 'NetworkError';
        this.statusCode = statusCode;
    }
}

function validateEmail(email) {
    if (!email.includes('@')) {
        throw new ValidationError('Invalid email format', 'email');
    }
}

try {
    validateEmail('bad');
} catch (err) {
    if (err instanceof ValidationError) {
        showFieldError(err.field, err.message);
    } else {
        throw err;
    }
}
  

Error Cause (ES2022)

Chain errors to preserve context:

  try {
    await fetchUser();
} catch (err) {
    throw new Error('Failed to load dashboard', { cause: err });
}

// Access chain
catch (err) {
    console.log(err.message);       // 'Failed to load dashboard'
    console.log(err.cause.message); // original error
}
  

Try/Catch Best Practices

  try {
    riskyOperation();
} catch (err) {
    logError(err);
    showUserMessage('Something went wrong. Please try again.');
} finally {
    hideLoadingSpinner(); // always runs
}
  
  • Catch specific errors when possible
  • Don’t swallow errors silently — log or re-throw
  • Use finally for cleanup (close connections, hide spinners)

Async Error Handling

Promises

  fetch('/api/data')
    .then(r => {
        if (!r.ok) throw new NetworkError('Request failed', r.status);
        return r.json();
    })
    .catch(err => handleError(err));

// Unhandled rejection — always attach catch
async function load() {
    try {
        await fetchData();
    } catch (err) {
        handleError(err);
    }
}
  

Global Unhandled Rejection

  window.addEventListener('unhandledrejection', (event) => {
    console.error('Unhandled promise rejection:', event.reason);
    event.preventDefault(); // prevent default console error
    reportToErrorService(event.reason);
});
  

In Node.js: process.on('unhandledRejection', ...).

Global Error Handlers

  window.onerror = (message, source, lineno, colno, error) => {
    reportToErrorService({ message, source, lineno, colno, stack: error?.stack });
    return true; // prevent default browser error display (optional)
};

window.addEventListener('error', (event) => {
    if (event.target instanceof HTMLScriptElement) {
        console.error('Script failed to load:', event.target.src);
    }
}, true); // capture phase for resource errors
  

Error Boundaries Pattern (Concept)

In React, Error Boundaries catch render errors. In vanilla JS, wrap component init:

  function safeInit(InitFn) {
    try {
        InitFn();
    } catch (err) {
        console.error('Init failed:', err);
        document.body.innerHTML = '<p>Failed to load application.</p>';
    }
}
  

Result Type Pattern

Avoid exceptions for expected failures:

  async function fetchUser(id) {
    try {
        const response = await fetch(`/api/users/${id}`);
        if (!response.ok) {
            return { ok: false, error: `HTTP ${response.status}` };
        }
        const data = await response.json();
        return { ok: true, data };
    } catch (err) {
        return { ok: false, error: err.message };
    }
}

const result = await fetchUser(1);
if (result.ok) {
    renderUser(result.data);
} else {
    showError(result.error);
}
  

Logging and Monitoring

  function reportError(error, context = {}) {
    const payload = {
        message: error.message,
        stack: error.stack,
        name: error.name,
        cause: error.cause?.message,
        url: location.href,
        userAgent: navigator.userAgent,
        timestamp: new Date().toISOString(),
        ...context
    };

    // Send to Sentry, Datadog, etc.
    if (window.Sentry) {
        Sentry.captureException(error, { extra: context });
    }
}
  

Never log passwords, tokens, or PII.

User-Facing Error Messages

Internal User sees
ECONNREFUSED “Unable to connect. Check your internet.”
ValidationError: email “Please enter a valid email address.”
HTTP 500 “Something went wrong on our end. Try again later.”

Map technical errors to actionable user messages.

Assertions (Development)

  function assert(condition, message) {
    if (!condition) {
        throw new Error(`Assertion failed: ${message}`);
    }
}

// Node 18+: import assert from 'node:assert/strict';
  

Remove or disable assertions in production builds.

Debugging Strategies

  1. Reproduce minimally — isolate failing code path
  2. Read stack trace bottom-up — find origin
  3. Log structured context — inputs, state, user actions before error
  4. Use breakpoints — conditional breaks on error paths
  5. Binary search — comment out half the code to locate issue

Best Practices

  1. Fail fast — validate inputs early
  2. Centralize error reporting — one reportError function
  3. Never expose stack traces to users in production
  4. Handle async errors — try/catch with await, .catch on chains
  5. Test error paths — not just happy paths

Troubleshooting

Error swallowed with empty catch

  • Log or re-throw; empty catch {} hides bugs

Stack trace unhelpful (minified)

  • Use source maps in production error reporting

Intermittent async errors

  • Race conditions — add logging with timestamps

Robust error handling separates prototype code from production-ready applications.