Prompt
Implement robust error handling that provides fallback mechanisms, ensures proper resource cleanup, and delivers user-friendly error messages without exposing internal details.
Key principles:
- Use finally blocks for cleanup: When modifying global state or resources, always reset them in finally blocks to prevent resource leaks when errors occur
- Provide fallback mechanisms: When primary operations fail (like JSON serialization), implement fallback strategies rather than failing completely
- Check error types, not messages: Use
instanceof Erroror specific error types rather than fragile string matching on error messages - Handle unexpected scenarios gracefully: When encountering unexpected conditions, log debug information and provide user-friendly error messages
- Avoid exposing internal details: Don’t leak internal error messages or stack traces to external users; provide sanitized, actionable error messages
Example implementation:
// Good: Proper cleanup and fallback
export async function processWithCleanup(config: Config): Promise<string> {
let originalValue;
try {
originalValue = global.someProperty;
global.someProperty = newValue;
const result = await riskyOperation();
// Fallback for serialization
try {
return JSON.stringify(result, null, 2);
} catch (serializationError) {
debug('JSON.stringify failed, trying without pretty print', serializationError);
return JSON.stringify(result);
}
} catch (error) {
// Type-based error handling
if (error instanceof CustomError) {
return handleCustomError(error);
}
// Graceful handling of unexpected errors
debug(`Unexpected error: ${error}`);
throw new UserFriendlyError('Operation failed. Please try again or contact support.');
} finally {
// Always cleanup, regardless of success or failure
if (originalValue !== undefined) {
global.someProperty = originalValue;
}
}
}
This approach ensures applications remain stable and user-friendly even when encountering unexpected conditions or failures.