JS Silent Errors
In this page:
What Makes an Error "Silent"
A silent error produces no crash, no visible error message, and no obvious sign anything went wrong -- the code simply does not do what was intended, which can be far more confusing to debug than a loud crash with a clear stack trace pointing at the problem.
Note: When something "just does not work" with no visible error, suspect a silent failure first -- check for swallowed exceptions, unhandled rejections, or a condition quietly evaluating to something unexpected.
Warning: A silent error can go unnoticed in production for a long time, since nothing alerts anyone that it is happening, unlike a loud crash that users or monitoring tools would immediately report.
Example: What Makes an Error "Silent"
function divide(a, b) {
if (b === 0) return; // no crash, no error - just wrong silently
return a / b;
}
console.log(divide(10, 0)); // undefined, no indication of why
Unhandled Promise Rejections
A Promise that rejects with no .catch() (or an async function whose error is never caught) produces an "unhandled rejection" -- the browser typically logs a warning to the console, but nothing in the running application actually reacts to or surfaces the failure to the user.
Note: Always attach a .catch() (or wrap awaits in try/catch) to any Promise chain your code cares about, even if the handler just logs the error for now.
Warning: An unhandled rejection's console warning is easy to miss during development, and users never see any console output at all -- the failure can go completely unnoticed in production.
Example: Unhandled Promise Rejections
Promise.reject("Something failed"); // no .catch() - unhandled rejection warning only, nothing reacts
Empty or Overly Broad Catch Blocks
A catch block that does nothing with the caught error -- catch (e) {} -- suppresses the failure entirely, effectively hiding it from view. This is sometimes done deliberately for a genuinely optional operation, but far more often it accidentally masks a real problem worth investigating.
Note: At minimum, log a caught error (even during early development) rather than leaving a catch block empty, so a real problem does not silently disappear.
Warning: An empty catch block used "temporarily" during development is easy to forget about and leave in place, permanently hiding whatever errors it happens to catch.
Example: Empty or Overly Broad Catch Blocks
try {
JSON.parse("bad json");
} catch (e) {} // swallowed silently, hides a real problem
console.log("Continues as if nothing happened");
Silent Failures from Optional Chaining and ??
Optional chaining (?.) and nullish coalescing (??) are excellent for genuinely optional data, but overusing them everywhere can mask a case where a value being missing actually indicates a real bug -- silently falling back to undefined or a default instead of surfacing the unexpected condition.
Note: Reserve ?. and ?? specifically for values that are genuinely, legitimately optional -- for values that should always be present, let a missing one surface as a clear error instead.
Warning: Using ?. defensively 'just in case' everywhere in a codebase can hide the exact spot where a value unexpectedly became missing, making the eventual bug much harder to trace back to its source.
Example: Silent Failures from Optional Chaining and ??
const user = { profile: null };
console.log(user.profile?.name ?? "Unknown"); // masks whether this is expected or a real bug
Detecting Silent Failures
Tools exist specifically to surface silent failures: the browser's unhandledrejection event fires for any Promise rejection with no .catch(), and linters can flag empty catch blocks or missing return statements automatically, catching many silent-failure patterns before they ever reach production.
Note: Add a global window.addEventListener("unhandledrejection", ...) handler during development (or in production error tracking) to catch any Promise rejection that slips through without a specific handler.
Warning: Relying purely on manual code review to catch every silent-failure pattern is unreliable at scale -- automated tools (linters, error tracking services) catch far more of these consistently.
Example: Detecting Silent Failures
window.addEventListener("unhandledrejection", (e) => console.log("Caught silent rejection:", e.reason));
Promise.reject("oops");
- Writing an async function or Promise chain with no .catch() (or try/catch around await), letting a rejection disappear silently instead of surfacing as a visible error.
- Using optional chaining (?.) so liberally that a genuinely unexpected missing property silently returns undefined instead of surfacing as a helpful error close to its actual source.
- Catching an error broadly and doing nothing with it (an empty catch block), effectively swallowing information that would have helped diagnose a real problem.
- A silent error is one that fails without any visible crash, warning, or console output -- often harder to notice and debug than a loud error.
- An unhandled Promise rejection (no .catch()) is a very common source of silent failures in asynchronous code.
- An empty or overly broad catch block can accidentally suppress genuinely useful error information.
The browser's unhandledrejection event and console warnings for silent failures are supported in every modern browser, helping surface issues that would otherwise go unnoticed.
Chapter Quiz — Complete all 6 topics to unlock
0/6 topics done
Complete these topics first: