Handle JavaScript errors with try catch and window.onerror in WordPress themes

JavaScript errors that are not caught crash the script execution at the point of the error, leaving the rest of your code — and any user-facing functionality that depends on it — silently broken. A user sees nothing; the browser console shows a red error that no one in production will ever read. Proper error handling means catching errors where they can occur, logging them where they can be acted on, and providing a graceful fallback to the user. The try/catch/finally block is the synchronous error boundary: wrap any code that could throw — JSON parsing, DOM manipulation on potentially-absent elements, third-party API calls — in a try block and handle the caught Error object in catch. For async code using async/await, the same try/catch syntax works because await unwraps rejected Promises and throws them as catchable errors. For Promise chains, attach a .catch() handler. For completely unhandled errors (code you did not write, third-party scripts), use window.onerror (synchronous) and window.addEventListener(‘unhandledrejection’) (async Promises). These global handlers are your last line of defence and are the entry point for client-side error monitoring services like Sentry. Pair this with the Promises and async/await guide for a complete picture of JavaScript async error handling in WordPress themes.

Problem: Uncaught JavaScript errors silently break functionality on your WordPress site and you have no way to detect or report them in production.

Solution: Add the following error handling patterns to your theme’s JavaScript file:

// Synchronous try/catch/finally
function parseUserData(raw) {
    try {
        const data = JSON.parse(raw);          // throws SyntaxError on invalid JSON
        return data;
    } catch (err) {
        console.error('parseUserData failed:', err.message);
        return {};                             // safe fallback
    } finally {
        // runs whether or not an error was thrown (cleanup goes here)
    }
}

// Async/await error handling
async function loadPosts(page) {
    try {
        const res = await fetch(`/wp-json/wp/v2/posts?page=${page}`);
        if (!res.ok) {
            throw new Error(`HTTP ${res.status}: ${res.statusText}`);
        }
        return await res.json();
    } catch (err) {
        if (err.name === 'AbortError') return [];     // user navigated away
        console.error('loadPosts error:', err);
        showUserMessage('Could not load posts. Please try again.');
        return [];
    }
}

// Promise chain .catch()
fetch('/wp-json/wp/v2/categories')
    .then(res => res.json())
    .then(cats => renderCategories(cats))
    .catch(err => console.error('Categories fetch failed:', err));

// Global handler: catch all unhandled synchronous errors
window.onerror = function(message, source, line, col, error) {
    // Send to your error tracking endpoint
    navigator.sendBeacon('/wp-admin/admin-ajax.php', JSON.stringify({
        action : 'helloadmin_js_error',
        message: message,
        source : source,
        line   : line,
        stack  : error ? error.stack : '',
    }));
    return false; // false = still show in console; true = suppress
};

// Global handler: catch unhandled Promise rejections
window.addEventListener('unhandledrejection', (event) => {
    console.error('Unhandled promise rejection:', event.reason);
    event.preventDefault(); // suppress default browser logging if needed
});

NOTE: Never use catch (err) {} (empty catch block) — silently swallowing errors makes debugging extremely difficult. At minimum, log to console.error. In production, integrate a proper error monitoring service like Sentry (free tier available) which captures the full stack trace, browser version, and user session context automatically — far more useful than the global window.onerror approach above. The navigator.sendBeacon() call in the global handler is intentionally non-blocking and queues the error report even if the user navigates away immediately after the error fires.