DEV Community

Dedicated Coder
Dedicated Coder

Posted on

Smash Stories: How I Stopped an AI API From Crashing My Web App

This is a submission for DEV's Summer Bug Smash: Smash Stories powered by Sentry.

The Chaotic Bug 🐛

I recently built a frontend web application called the Sarcastic AI Code Reviewer. It uses a free, public open-source LLM endpoint via the Hugging Face Inference API to roast nested "div soup" markup.

Everything worked perfectly in local testing. But public APIs have a major real-world bottleneck: Rate limits and network instability.

When testing heavy traffic simulations, the API would occasionally time out or throw a 503 Service Unavailable error. Without proper guardrails, the application would break silently:

  1. The "Analyze My Code" button would stay disabled forever (disabled = true) with the text "Consulting the angry senior developer...".
  2. The user would be left staring at a frozen screen, unable to try again.
  3. The console would light up red with unhandled promise rejections.

It was a classic UX nightmare—a broken app state caused by a third-party dependency.


The Fix 🛠️

To make the application resilient, I implemented a robust try...catch...finally block inside the async event listener.

Here is the exact defensive JavaScript implementation I used to smash this bug:

roastBtn.addEventListener('click', async () => {
  const codeToRoast = codeInput.value.trim();

  if (!codeToRoast) return;

  // 1. Instantly update UI state
  roastBtn.disabled = true;
  roastBtn.innerText = "Consulting the angry senior developer...";
  aiResponse.innerText = "Analyzing your digital sins...";

  try {
    const response = await fetch(API_URL, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ inputs: userMessage })
    });

    const data = await response.json();

    if (data && data[0] && data[0].generated_text) {
      aiResponse.innerText = data[0].generated_text.trim();
    } else {
      throw new Error("Invalid API Payload");
    }

  } catch (error) {
    // 2. The Smash: Catch network/API failures gracefully
    console.error("Caught API Error:", error);

    // Serve a funny, thematic error message instead of a blank screen
    aiResponse.innerText = "🤖 [SYSTEM ERROR] The AI was so overwhelmed by your code that its API crashed. Try clicking the button again to wake it up!";

  } finally {
    // 3. The Ultimate Safeguard: Always restore the UI state, win or lose!
    roastBtn.disabled = false;
    roastBtn.innerText = "Analyze My Code";
  }
}); 


Enter fullscreen mode Exit fullscreen mode

🧠 The Lessons Learned:
By strategically structuring the code, I achieved two major wins:
The Power of finally: No matter if the network request succeeds beautifully or crashes spectacularly, the finally block always executes. This guarantees the submit button unlocks itself automatically, meaning the user is never permanently stuck.
Graceful Degradation: Instead of letting the app fail silently, catching the error allowed me to maintain the application's unique voice by delivering a humorous custom error message.

📚 Moral of the story:
Never assume a third-party API will work 100% of the time. Code defensively, protect your UI states, and wrap your async actions tightly!


🔗 Project Links


✍️ About the Author

Top comments (0)