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
- Script Creation:
I useddocument.createElement("script")
to create a new<script>
element. - 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.
- language and type: These indicate that the script is in JavaScript (note that the
- Appending the Script:
I appended the script to the<head>
element usinghead.appendChild(element)
. - Error Handling with try…catch:
I wrapped the process in atry...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:
onerror
captures:- Failed network requests (external scripts)
- Syntax errors
- Runtime errors during execution
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
- 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. - 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.
- 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 treatscriptContent
as a URL (true
) or as inline code (false
).
- 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.