Site icon FSIBLOG

How To Fix JavaScript Error on a Page / JavaScript Error Handling

How To Fix JavaScript Error on a Page / JavaScript Error Handling

How To Fix JavaScript Error on a Page / JavaScript Error Handling

When I first started writing JavaScript, nothing frustrated me more than those mysterious red errors popping up in the console. Sometimes they stopped my code cold; other times they quietly broke parts of my page. Over time, I realized that the key wasn’t just fixing errors when they appeared, but building a reliable way to capture and handle them.

I’ll walk you through a complete, drop-in script that I use in both web pages and browser extensions. It catches almost everything: runtime errors, broken resources, failed promises, and even logs from console.error. It also adds helpful tools like retry-with-backoff for fragile code. Finally, I’ll share practical ways to report those errors back to yourself either to a server or to an extension background script.

Why Error Happen

JavaScript errors usually fall into three buckets:

  1. Runtime errors — things like ReferenceError: nonExistentCall is not defined.
  2. Resource errors — a missing script, stylesheet, or image.
  3. Promise rejections — failed fetch calls or async code that throws without a handler.

The problem is that once one of these fires, it can stop execution in unexpected ways. That’s why I needed a solution that:

The Complete Script

Here’s the full script. You can paste this into a <script> tag on your page or bundle it inside a browser extension content script.

<script>
/** ========= CONFIG ========= **/
const ERROR_ENDPOINT = "/error-intake";         // your server endpoint (if using fetch)
const EXT_MSG_TYPE   = "CLIENT_ERROR";          // message type if using chrome.runtime.sendMessage
const MAX_BUFFER     = 20;                      // max buffered reports before flush
const MAX_RETRIES    = 5;                       // defExe max attempts
const BASE_DELAY_MS  = 250;                     // defExe base delay (exponential backoff)
const ENABLE_FETCH   = false;                   // set true if you have a server to receive errors
const ENABLE_EXTMSG  = typeof chrome !== "undefined" && chrome.runtime?.id;

/** ========= UTILITIES ========= **/
function nowISO(){ return new Date().toISOString(); }

function extraInfo() {
  return {
    url: location.href,
    userAgent: navigator.userAgent,
    platform: navigator.platform,
    language: navigator.language || navigator.userLanguage,
    cookiesEnabled: navigator.cookieEnabled,
    viewport: { w: window.innerWidth, h: window.innerHeight },
    time: nowISO()
  };
}

/** ========= TRANSPORTS ========= **/
async function mailError(payload) {
  ErrorBuffer.push(payload);
  if (ErrorBuffer.length >= MAX_BUFFER) await flushErrors();
}

async function flushErrors() {
  if (ErrorBuffer.length === 0) return;
  const batch = ErrorBuffer.splice(0, ErrorBuffer.length);
  try {
    if (ENABLE_FETCH) {
      await fetch(ERROR_ENDPOINT, {
        method: "POST",
        headers: {"Content-Type":"application/json"},
        keepalive: true,
        body: JSON.stringify({ batch })
      });
    }
    if (ENABLE_EXTMSG) {
      chrome.runtime.sendMessage({ type: EXT_MSG_TYPE, batch });
    }
  } catch (e) {
    ErrorBuffer.unshift(...batch);
  }
}

const ErrorBuffer = [];
for (const ev of ["visibilitychange","pagehide","beforeunload"]) {
  window.addEventListener(ev, () => { if (document.visibilityState !== "visible") flushErrors(); });
}

/** ========= DOM READINESS ========= **/
function onReady(cb){
  if (document.readyState === "complete" || document.readyState === "interactive") {
    queueMicrotask(cb);
  } else {
    document.addEventListener("DOMContentLoaded", cb, { once:true });
  }
}

/** ========= SAFE DOM HELPERS ========= **/
function showIfExists(id){
  const el = document.getElementById(id);
  if (el) {
    el.style.display = "block";
    return true;
  } else {
    mailError({
      type: "DOM_MISS",
      message: `Element #${id} not found`,
      meta: extraInfo()
    });
    return false;
  }
}

/** ========= CONSOLE WRAPPER ========= **/
const Xe = {
  error(msg, meta={}) {
    console.error(msg);
    mailError({ type:"CONSOLE_ERROR", message: String(msg), meta: {...extraInfo(), ...meta} });
  },
  warn(msg, meta={}) {
    console.warn(msg);
    mailError({ type:"CONSOLE_WARN", message: String(msg), meta: {...extraInfo(), ...meta} });
  },
  log(msg, meta={}) {
    console.log(msg);
    mailError({ type:"CONSOLE_LOG", message: String(msg), meta: {...extraInfo(), ...meta} });
  }
};

/** ========= GLOBAL ERROR CAPTURE ========= **/
window.onerror = function(message, source, lineno, colno, error){
  mailError({
    type: "ONERROR",
    message: String(message),
    source, lineno, colno,
    stack: error && error.stack,
    meta: extraInfo()
  });
  return true;
};

window.addEventListener("error", function(ev){
  const t = ev.target;
  if (t && t !== window && (t.src || t.href)) {
    mailError({
      type: "RESOURCE_ERROR",
      message: "Resource failed to load",
      tag: t.tagName,
      url: t.src || t.href,
      meta: extraInfo()
    });
  }
}, true);

window.addEventListener("unhandledrejection", function(ev){
  const reason = ev.reason || {};
  mailError({
    type: "UNHANDLED_REJECTION",
    message: String(reason.message || reason),
    stack: reason.stack,
    meta: extraInfo()
  });
});

/** ========= “DEFINITELY EXECUTE” with BACKOFF ========= **/
function defExe(fn, opts = {}) {
  const { max = MAX_RETRIES, baseDelay = BASE_DELAY_MS } = opts;
  let attempt = 0;
  const run = () => {
    try {
      fn();
    } catch (e) {
      attempt += 1;
      mailError({
        type: "DEFEXE_CATCH",
        message: `Attempt ${attempt}/${max} failed: ${e && e.message}`,
        stack: e && e.stack,
        fnName: fn.name || "anonymous",
        meta: extraInfo()
      });
      if (attempt < max) {
        const delay = baseDelay * Math.pow(2, attempt - 1);
        setTimeout(run, delay);
      } else {
        Xe.error(`defExe giving up after ${max} attempts for ${fn.name || "anonymous"}`);
      }
    }
  };
  run();
}

/** ========= EXAMPLE USAGE ========= **/
onReady(() => {
  showIfExists("iNeedThisElement");
  function initWidgets(){
    const el = document.querySelector("[data-widget]");
    if (!el) throw new Error("widget root missing");
    el.textContent = "Widget initialized at " + nowISO();
  }
  defExe(initWidgets);
  Xe.warn("Demo warning");
  Xe.log("Demo log");
});
</script>

How It Works

Extra Tricks I Use

Final Thought

JavaScript errors will always happen it’s the nature of dynamic code, unpredictable networks, and fragile DOM states. What matters is how we capture, report, and recover from them. This script has saved me countless hours of head-scratching. Instead of wondering why a widget silently broke, I get a clear log with stack traces, environment info, and retry attempts. Whether you’re building a simple website or a complex browser extension, a robust error handling layer like this makes your code far more resilient.

Exit mobile version