Guides error handling for async operations and API calls...
"Errors are not failures — they're information. Handle them like the valuable data they are."
Activate this skill when reviewing:
❌ try {
await submitForm();
} catch (error) {
// TODO: handle later (never happens)
}
✅ try {
await submitForm();
} catch (error) {
console.error('Form submission failed:', error);
setError('Could not submit. Please try again.');
}
❌ function getUser(id) {
try {
return database.query(id);
} catch {
return null; // Caller has no idea it failed
}
}
✅ function getUser(id) {
return database.query(id); // Let caller decide
}
// In UI layer
try {
const user = await getUser(id);
} catch (error) {
showToast('Could not load user');
}
❌ catch (error) {
setError('An error occurred');
}
✅ catch (error) {
if (error.code === 'NETWORK_ERROR') {
setError('Check your internet connection');
} else if (error.code === 'NOT_FOUND') {
setError('User not found');
} else {
setError('Something went wrong. Please try again.');
}
}
❌ res.status(500).json({
error: error.message,
stack: error.stack
});
✅ logger.error('Request failed', { error, requestId });
res.status(500).json({
error: 'Something went wrong'
});
❌ try {
setLoading(true);
await fetchData();
setLoading(false);
} catch (error) {
handleError(error);
// Loading stays true forever!
}
✅ try {
setLoading(true);
await fetchData();
} catch (error) {
handleError(error);
} finally {
setLoading(false); // Always runs
}
Ask the junior these questions instead of giving answers:
TypeError: Cannot read property 'map' of undefinedasync function fetchWithRetry(url: string, retries = 3): Promise<Response> {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response;
} catch (error) {
if (retries > 0) {
await sleep(1000);
return fetchWithRetry(url, retries - 1);
}
throw error;
}
}
function validateForm(data: FormData) {
const errors: Record<string, string> = {};
if (!data.email) {
errors.email = 'Email is required';
} else if (!isValidEmail(data.email)) {
errors.email = 'Please enter a valid email';
}
return {
isValid: Object.keys(errors).length === 0,
errors
};
}
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, info) {
logError(error, info);
}
render() {
if (this.state.hasError) {
return <FallbackUI />;
}
return this.props.children;
}
}
See detailed patterns in:
/standards/global/error-handling.md| Flag | Question to Ask |
|---|---|
| Empty catch block | "What happens when this fails?" |
catch (e) { return null } |
"How will the caller know it failed?" |
| No loading state | "What does the user see while waiting?" |
| Technical error shown | "Will the user understand this message?" |
| No finally for cleanup | "Is loading state stuck on error?" |
| console.log instead of error | "How will you find this in production logs?" |