How Can I Fix a Catch Script Errors in JavaScript

Hi fellow devs! Let me tell you about the time I tried to catch errors in dynamically injected JavaScript and why my trusty try-catch block completely betrayed me. Here’s my journey from confusion to solution, complete with code examples and hard-earned wisdom.

My Original Code and What I Learned

I started with a simple piece of code that created a <script> element, set its attributes, and then appended it to the <head> of my document. Here’s the code I wrote:

try {
var element = document.createElement("script");
element.language = "javascript";
element.type = "text/javascript";
element.defer = true;
element.text = "this is not a javascript code";
var head = document.getElementsByTagName('head')[0];
head.appendChild(element);
} catch(err) {
alert("error caught");
}

What This Code Does

  1. Script Creation:
    I used document.createElement("script") to create a new <script> element.
  2. Attributes Setting:
    I set several attributes on the element:
    • language and type: These indicate that the script is in JavaScript (note that the language attribute is deprecated in modern HTML).
    • defer: This instructs the browser to execute the script after the document has been parsed.
    • text: This contains the code to be executed—in my case, a string that intentionally isn’t valid JavaScript.
  3. Appending the Script:
    I appended the script to the <head> element using head.appendChild(element).
  4. Error Handling with try…catch:
    I wrapped the process in a try...catch block, expecting it to catch any errors. However, I quickly discovered that while the block can catch synchronous errors (like issues during element creation or appending), it doesn’t catch errors that occur later when the script is executed asynchronously.

Understanding the Failure: Sync vs Async

Here’s what I learned through painful debugging:

  • Synchronous Success: The try block only catches errors during script element creation and insertion
graph LR
A[createElement] --> B[setAttributes]
B --> C[appendChild]
C --> D[Browser processes script]
  • The try catches errors in A→C
  • It doesn’t see errors in D (script execution)
  • Execution Timeline:
try {
// Sync operations (instant)
createElement()
setAttributes()
appendChild()
} catch {
// Only catches sync errors here
}

// Browser executes script LATER
// (completely outside try scope)

Event-Based Error Handling

Here’s how I modified my approach to catch all errors:

function loadScript(code, isExternal = false) {
  const script = document.createElement('script');
  
  // Error handler for script lifecycle
  script.onerror = (event) => {
    console.error('Script failure:', {
      error: event.error,
      line: event.lineno,
      file: event.filename
    });
  };

  // Success handler
  script.onload = () => {
    console.log('Script executed successfully!');
  };

  // Set content
  isExternal ? (script.src = code) : (script.text = code);

  document.head.appendChild(script);
}

Key Improvements:

  1. onerror captures:
    • Failed network requests (external scripts)
    • Syntax errors
    • Runtime errors during execution
  2. onload confirms successful execution

Real-World Example: Loading Analytics

Here’s how I implemented this for a production analytics integration:

async function initAnalytics() {
  try {
    // Load SDK
    await loadScript('https://analytics-provider.com/sdk.js', true);
    
    // Initialize
    window.analytics.init(API_KEY);
    
  } catch (error) {
    // Fallback to local tracking
    console.warn('Analytics failed, using fallback');
    localTracker.trackEvent('sdk_failure');
  }
}

Breaking Down the Extended Code

  1. Function addScriptWithErrorHandling:
    I encapsulated the functionality into a function that supports both inline and external scripts. This makes it easier to reuse and test with different scripts.
  2. Event Handlers:
    • onerror: This event handler catches any errors during the script’s loading or execution. If something goes wrong (like a syntax error or a failed load), I log the error and show an alert.
    • onload: This handler confirms that the script has loaded and executed successfully, providing positive feedback.
  3. Parameters:
    • scriptContent: This parameter holds either the inline code or the URL of an external script.
    • isExternal: This boolean flag tells the function whether it should treat scriptContent as a URL (true) or as inline code (false).
  4. Usage Examples:
    • The first example attempts to execute an invalid inline script, which triggers the error handler.
    • The second example simulates an error by trying to load a non-existent external script.

Final Thoughts

This experience taught me that browser script handling operates on a different timeline than regular JavaScript execution. By embracing event-driven patterns instead of relying solely on try-catch, I now build more resilient applications.

Related blog posts