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.