Standardized error classes with error codes for consistent error handling across Expo modules.
General-purpose error class with error codes for categorizing and handling different types of errors.
/**
* A general error class that should be used for all errors in Expo modules
* Guarantees a code field for error differentiation without subclassing
*/
class CodedError extends Error {
/** Error code for programmatic error identification */
code: string;
/** Additional error information */
info?: any;
/**
* Creates a new coded error
* @param code - Unique error code for this error type
* @param message - Human-readable error message
*/
constructor(code: string, message: string);
}Usage Examples:
import { CodedError } from "expo-modules-core";
// Create custom error types using CodedError
function validateUserInput(input: string): void {
if (!input) {
throw new CodedError("VALIDATION_ERROR", "Input cannot be empty");
}
if (input.length < 3) {
throw new CodedError("VALIDATION_ERROR", "Input must be at least 3 characters");
}
if (!/^[a-zA-Z0-9]+$/.test(input)) {
throw new CodedError("INVALID_FORMAT", "Input contains invalid characters");
}
}
// Handle errors by code
try {
validateUserInput("ab");
} catch (error) {
if (error instanceof CodedError) {
switch (error.code) {
case "VALIDATION_ERROR":
console.log("Validation failed:", error.message);
break;
case "INVALID_FORMAT":
console.log("Format error:", error.message);
break;
default:
console.log("Unknown error:", error.message);
}
}
}
// Add additional error information
class NetworkError extends CodedError {
constructor(message: string, statusCode: number, url: string) {
super("NETWORK_ERROR", message);
this.info = { statusCode, url };
}
}
// Usage with additional info
try {
throw new NetworkError("Request failed", 404, "https://api.example.com/users");
} catch (error) {
if (error instanceof CodedError && error.code === "NETWORK_ERROR") {
console.log(`Network error ${error.info.statusCode}: ${error.message}`);
console.log(`Failed URL: ${error.info.url}`);
}
}Specialized error for handling unavailable platform functionality with automatic platform detection.
/**
* Error thrown when a property is accessed that is unavailable, unsupported,
* or not currently implemented on the running platform
*/
class UnavailabilityError extends CodedError {
/**
* Creates an unavailability error with platform-specific message
* @param moduleName - Name of the module containing the unavailable functionality
* @param propertyName - Name of the unavailable property or method
*/
constructor(moduleName: string, propertyName: string);
}Usage Examples:
import { UnavailabilityError, Platform } from "expo-modules-core";
// Throw unavailability errors for unsupported functionality
class LocationModule {
getCurrentPosition(): Promise<Location> {
if (Platform.OS === 'web' && !navigator.geolocation) {
throw new UnavailabilityError("LocationModule", "getCurrentPosition");
}
// Implementation for supported platforms
return this.nativeGetCurrentPosition();
}
startBackgroundUpdates(): void {
if (Platform.OS === 'web') {
throw new UnavailabilityError("LocationModule", "startBackgroundUpdates");
}
// Implementation for native platforms only
this.nativeStartBackgroundUpdates();
}
}
// Handle unavailability errors
const locationModule = new LocationModule();
try {
await locationModule.getCurrentPosition();
} catch (error) {
if (error instanceof UnavailabilityError) {
console.log("Feature not available on this platform:", error.message);
// Provide alternative or disable feature
showUnavailableFeatureMessage();
} else {
console.error("Unexpected error:", error);
}
}
// Check for specific unavailability
function isFeatureUnavailable(error: any): boolean {
return error instanceof UnavailabilityError ||
(error instanceof CodedError && error.code === "ERR_UNAVAILABLE");
}
// Graceful degradation pattern
async function getLocationWithFallback(): Promise<Location | null> {
try {
return await locationModule.getCurrentPosition();
} catch (error) {
if (isFeatureUnavailable(error)) {
console.log("Location unavailable, using IP-based location");
return await getIPLocation();
}
throw error; // Re-throw if not unavailability error
}
}Common patterns for handling Expo module errors effectively.
import { CodedError, UnavailabilityError } from "expo-modules-core";
// Comprehensive error handling utility
class ErrorHandler {
static handle(error: any, context: string): void {
if (error instanceof UnavailabilityError) {
console.warn(`Feature unavailable in ${context}:`, error.message);
this.showUnavailableFeatureDialog(context);
} else if (error instanceof CodedError) {
console.error(`Coded error in ${context}:`, error.code, error.message);
this.handleCodedError(error, context);
} else if (error instanceof Error) {
console.error(`General error in ${context}:`, error.message);
this.handleGeneralError(error, context);
} else {
console.error(`Unknown error in ${context}:`, error);
this.handleUnknownError(error, context);
}
}
private static handleCodedError(error: CodedError, context: string): void {
switch (error.code) {
case "PERMISSION_DENIED":
this.showPermissionDialog();
break;
case "NETWORK_ERROR":
this.showNetworkErrorDialog();
break;
case "VALIDATION_ERROR":
this.showValidationErrorDialog(error.message);
break;
default:
this.showGenericErrorDialog(error.message);
}
}
private static showUnavailableFeatureDialog(context: string): void {
// Implementation for showing unavailable feature dialog
}
private static showPermissionDialog(): void {
// Implementation for permission error dialog
}
private static showNetworkErrorDialog(): void {
// Implementation for network error dialog
}
private static showValidationErrorDialog(message: string): void {
// Implementation for validation error dialog
}
private static showGenericErrorDialog(message: string): void {
// Implementation for generic error dialog
}
private static handleGeneralError(error: Error, context: string): void {
// Implementation for general error handling
}
private static handleUnknownError(error: any, context: string): void {
// Implementation for unknown error handling
}
}
// Module-specific error handling
class CameraModule {
private static throwError(code: string, message: string, additionalInfo?: any): never {
const error = new CodedError(code, message);
if (additionalInfo) {
error.info = additionalInfo;
}
throw error;
}
async takePicture(): Promise<Photo> {
try {
// Check permissions first
const permission = await this.checkPermissions();
if (!permission.granted) {
this.throwError(
"PERMISSION_DENIED",
"Camera permission not granted",
{ permission }
);
}
// Check camera availability
if (!await this.isCameraAvailable()) {
throw new UnavailabilityError("CameraModule", "takePicture");
}
return await this.nativeTakePicture();
} catch (error) {
if (error instanceof CodedError || error instanceof UnavailabilityError) {
throw error; // Re-throw known errors
}
// Wrap unknown errors
this.throwError(
"CAMERA_ERROR",
`Failed to take picture: ${error.message}`,
{ originalError: error }
);
}
}
}
// Usage with comprehensive error handling
async function handleCameraCapture() {
const camera = new CameraModule();
try {
const photo = await camera.takePicture();
console.log("Photo captured:", photo);
return photo;
} catch (error) {
ErrorHandler.handle(error, "Camera Capture");
return null;
}
}Creating domain-specific error types using CodedError as a base.
import { CodedError } from "expo-modules-core";
// Authentication errors
class AuthenticationError extends CodedError {
constructor(message: string, reason?: string) {
super("AUTH_ERROR", message);
if (reason) {
this.info = { reason };
}
}
}
class TokenExpiredError extends AuthenticationError {
constructor(expiryTime: number) {
super("Token has expired", "TOKEN_EXPIRED");
this.info = { expiryTime, expiredAt: new Date(expiryTime) };
}
}
// Storage errors
class StorageError extends CodedError {
constructor(operation: string, key: string, reason: string) {
super("STORAGE_ERROR", `Storage ${operation} failed for key "${key}": ${reason}`);
this.info = { operation, key, reason };
}
}
// Network errors with retry information
class NetworkError extends CodedError {
constructor(
message: string,
statusCode?: number,
retryable: boolean = false
) {
super("NETWORK_ERROR", message);
this.info = { statusCode, retryable, timestamp: Date.now() };
}
isRetryable(): boolean {
return this.info?.retryable === true;
}
}
// Usage of custom error types
class UserService {
async getUser(userId: string): Promise<User> {
try {
const token = await this.getAuthToken();
const response = await fetch(`/api/users/${userId}`, {
headers: { Authorization: `Bearer ${token}` }
});
if (response.status === 401) {
throw new AuthenticationError("Invalid or expired token");
}
if (response.status === 403) {
throw new AuthenticationError("Insufficient permissions", "FORBIDDEN");
}
if (!response.ok) {
const retryable = response.status >= 500;
throw new NetworkError(
`Request failed with status ${response.status}`,
response.status,
retryable
);
}
return await response.json();
} catch (error) {
if (error instanceof CodedError) {
throw error; // Re-throw our custom errors
}
// Wrap unexpected errors
throw new NetworkError(`Unexpected error: ${error.message}`);
}
}
private async getAuthToken(): Promise<string> {
try {
const token = await this.storage.getItem("auth_token");
if (!token) {
throw new AuthenticationError("No authentication token found");
}
const payload = JSON.parse(atob(token.split('.')[1]));
if (payload.exp * 1000 < Date.now()) {
throw new TokenExpiredError(payload.exp * 1000);
}
return token;
} catch (error) {
if (error instanceof AuthenticationError) {
throw error;
}
throw new StorageError("get", "auth_token", error.message);
}
}
}
// Error handling with custom types
async function handleUserFetch(userId: string) {
const userService = new UserService();
try {
const user = await userService.getUser(userId);
return user;
} catch (error) {
if (error instanceof TokenExpiredError) {
console.log("Token expired at:", error.info.expiredAt);
// Redirect to login
return null;
} else if (error instanceof AuthenticationError) {
console.log("Auth error:", error.message);
// Handle authentication failure
return null;
} else if (error instanceof NetworkError && error.isRetryable()) {
console.log("Retryable network error, will retry...");
// Implement retry logic
return null;
} else if (error instanceof StorageError) {
console.log("Storage error:", error.info);
// Handle storage issues
return null;
} else {
console.error("Unexpected error:", error);
throw error;
}
}
}/**
* Base coded error interface
*/
interface CodedError extends Error {
code: string;
info?: any;
}
/**
* Unavailability error interface
*/
interface UnavailabilityError extends CodedError {
code: 'ERR_UNAVAILABLE';
}