Thrown errors and unhandled promise rejections must be intentional and represent exceptional conditions, not part of the normal "happy path" of code execution.
Do NOT use errors as a mechanism for normal control flow:
// BAD: Using errors for expected conditions
function getValue(key) {
const value = map.get(key);
if (!value) throw new Error('Key not found'); // Expected case!
return value;
}
// GOOD: Return a sentinel or optional value
function getValue(key) {
return map.get(key) ?? null;
}
Errors should be thrown only for:
Unreachable code paths (exhaustiveness checks):
switch (type) {
case 'a': return handleA();
case 'b': return handleB();
default: throw new Error('unreachable');
}
Invalid invariants (programmer errors, not user errors):
if (!this.args) throw new Error('messaging requires args to be set');
if (!(manager instanceof VideoOverlay)) throw new Error('invalid arguments');
Missing required dependencies:
if (!_messagingModuleScope) throw new Error('Messaging not initialized');
Never leave promises unhandled - always attach .catch() or use try/catch with await
Use Promise.reject() only when mimicking browser API behavior (e.g., web-compat.js returning DOMException):
// Mimicking native Share API behavior
if (!canShare(data)) return Promise.reject(new TypeError('Invalid share data'));
Handle async errors explicitly:
// GOOD: Explicit error handling
await this.setUserChoice(choice).catch((e) => console.error('error setting user choice', e));
// GOOD: Swallow expected failures intentionally
await fetchOptional().catch(() => {}); // Comment explaining why this is OK
Use assertion functions for type/state validation that throws on failure:
/**
* @returns {asserts event is CustomEvent<{kind: string, data: any}>}
*/
function assertCustomEvent(event) {
if (!('detail' in event)) throw new Error('none-custom event');
if (typeof event.detail.kind !== 'string') throw new Error('custom event requires detail.kind to be a string');
}
throw new Error(`'${method.toString()}' is not a method of feature '${this.name}'`);
| Anti-Pattern | Problem | Solution |
|---|---|---|
| Empty catch blocks | Silently swallows errors | Log or re-throw with context |
Throwing for expected null/undefined |
Using errors as control flow | Return optional/sentinel values |
| Unhandled promise chains | Leads to unhandled rejections | Add .catch() handler |
| Generic error messages | Hard to debug | Include specific context |
| Catching and ignoring all errors | Masks bugs | Catch specific error types |
The codebase enforces:
require-await - async functions must use awaitpromise/prefer-await-to-then - prefer async/await over .then()@typescript-eslint/await-thenable - only await promises