or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

app-utilities.mderror-handling.mdevent-communication.mdindex.mdnative-modules.mdnative-views.mdpermissions.mdplatform-utilities.mdshared-memory.mduuid-generation.md
tile.json

error-handling.mddocs/

Error Handling

Standardized error classes with error codes for consistent error handling across Expo modules.

Capabilities

CodedError Class

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}`);
  }
}

UnavailabilityError Class

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
  }
}

Error Handling Patterns

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;
  }
}

Custom Error Types

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;
    }
  }
}

Types

/**
 * Base coded error interface
 */
interface CodedError extends Error {
  code: string;
  info?: any;
}

/**
 * Unavailability error interface
 */
interface UnavailabilityError extends CodedError {
  code: 'ERR_UNAVAILABLE';
}