+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 98 of 355

โšก Promises in TypeScript: Type-Safe Asynchronous Code

Master TypeScript Promises with complete type safety, error handling, and advanced async patterns for bulletproof applications ๐Ÿš€

๐Ÿš€Intermediate
25 min read

Prerequisites

  • Basic TypeScript syntax and types ๐Ÿ“
  • Understanding of JavaScript Promises ๐Ÿค
  • Async/await fundamentals โฐ

What you'll learn

  • Master Promise<T> types and generic constraints โšก
  • Handle async errors with complete type safety ๐Ÿ›ก๏ธ
  • Build robust async workflows and patterns ๐Ÿ—๏ธ
  • Create type-safe promise utilities and helpers โœจ

๐ŸŽฏ Introduction

Welcome to the exciting world of type-safe asynchronous programming! ๐ŸŽ‰ In this guide, weโ€™ll explore how TypeScript transforms JavaScript Promises from potential sources of runtime errors into bulletproof, type-safe async operations.

Youโ€™ll discover how to harness TypeScriptโ€™s powerful type system to catch async errors at compile time, create robust data flows, and build applications that handle uncertainty with confidence. Whether youโ€™re fetching data from APIs ๐ŸŒ, processing files ๐Ÿ“, or orchestrating complex async workflows ๐Ÿ”„, mastering typed Promises is essential for modern TypeScript development.

By the end of this tutorial, youโ€™ll be writing async code thatโ€™s not just functional, but elegantly typed and impossible to break! ๐Ÿš€ Letโ€™s dive in! ๐ŸŠโ€โ™‚๏ธ

๐Ÿ“š Understanding TypeScript Promises

๐Ÿค” What are Typed Promises?

Think of a TypeScript Promise as a type-safe container for future values ๐Ÿ“ฆ. Itโ€™s like placing an order at a restaurant ๐Ÿ• - you get a receipt (the Promise) that guarantees youโ€™ll eventually receive your specific meal (the typed result), or youโ€™ll be told what went wrong (the typed error).

In TypeScript terms, Promise<T> provides:

  • โœจ Type-safe results - know exactly what youโ€™ll get when the promise resolves
  • ๐Ÿš€ Compile-time error checking - catch async mistakes before runtime
  • ๐Ÿ›ก๏ธ Intelligent autocomplete - IDE support for async return values
  • ๐Ÿ“ฆ Generic constraints - ensure promises return the right types

๐Ÿ’ก Why Use Typed Promises?

Hereโ€™s why typed promises are game-changers:

  1. Predictable Async Code ๐Ÿ”ฎ: Know what your async functions return
  2. Early Error Detection ๐Ÿšจ: Catch type mismatches before deployment
  3. Safer API Calls ๐ŸŒ: Ensure responses match expected interfaces
  4. Better Team Collaboration ๐Ÿ‘ฅ: Self-documenting async interfaces
  5. Refactoring Confidence ๐Ÿ”ง: Change async code without fear

Real-world example: When fetching user data from an API ๐Ÿ‘ค, typed promises ensure you canโ€™t accidentally treat a User object as a string!

๐Ÿ”ง Basic Promise Types and Patterns

๐Ÿ“ Promise<T> Fundamentals

Letโ€™s start with the core Promise types:

// ๐ŸŽฏ Basic Promise types
// Promise that resolves to a string
const fetchMessage = (): Promise<string> => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("Hello, TypeScript! ๐Ÿ‘‹");
    }, 1000);
  });
};

// Promise that resolves to a number
const calculateAsync = (a: number, b: number): Promise<number> => {
  return new Promise((resolve, reject) => {
    if (a < 0 || b < 0) {
      reject(new Error("Negative numbers not allowed! ๐Ÿšซ"));
      return;
    }
    
    // ๐Ÿงฎ Simulate async calculation
    setTimeout(() => {
      const result = a * b + Math.random() * 100;
      resolve(Math.round(result));
    }, 500);
  });
};

// Promise that resolves to a custom interface
interface UserProfile {
  id: string;
  name: string;
  email: string;
  avatar?: string;
  lastLogin: Date;
}

const fetchUserProfile = (userId: string): Promise<UserProfile> => {
  return new Promise((resolve, reject) => {
    // ๐Ÿ” Simulate API call validation
    if (!userId || userId.trim() === '') {
      reject(new Error("User ID is required! ๐Ÿ“"));
      return;
    }
    
    // ๐Ÿ“Š Mock successful response
    setTimeout(() => {
      const user: UserProfile = {
        id: userId,
        name: `User ${userId}`,
        email: `user${userId}@example.com`,
        avatar: `https://api.adorable.io/avatars/100/${userId}.png`,
        lastLogin: new Date()
      };
      
      resolve(user);
      console.log(`โœ… Fetched profile for user: ${user.name}`);
    }, 800);
  });
};

๐Ÿ’ก Explanation: Notice how each Promise is typed with Promise<T> where T is the exact type of data it will resolve to!

๐ŸŽฏ Error Handling with Types

TypeScript provides excellent support for typed error handling:

// ๐Ÿšจ Custom error types for better error handling
class NetworkError extends Error {
  constructor(
    message: string,
    public statusCode: number,
    public endpoint: string
  ) {
    super(message);
    this.name = 'NetworkError';
  }
}

class ValidationError extends Error {
  constructor(
    message: string,
    public field: string,
    public value: any
  ) {
    super(message);
    this.name = 'ValidationError';
  }
}

// ๐ŸŽฏ Type-safe promise with specific error types
const fetchDataWithTypedErrors = async (url: string): Promise<any> => {
  try {
    if (!url.startsWith('http')) {
      throw new ValidationError(
        'Invalid URL format ๐Ÿ”—', 
        'url', 
        url
      );
    }
    
    // ๐Ÿ“ก Simulate HTTP request
    const response = await fetch(url);
    
    if (!response.ok) {
      throw new NetworkError(
        `HTTP ${response.status}: ${response.statusText} ๐Ÿ“ก`,
        response.status,
        url
      );
    }
    
    const data = await response.json();
    console.log(`โœ… Successfully fetched data from: ${url}`);
    return data;
    
  } catch (error) {
    // ๐ŸŽฏ TypeScript helps us handle different error types
    if (error instanceof NetworkError) {
      console.error(`๐ŸŒ Network error (${error.statusCode}): ${error.message}`);
      throw error;
    } else if (error instanceof ValidationError) {
      console.error(`โš ๏ธ Validation error for ${error.field}: ${error.message}`);
      throw error;
    } else {
      console.error(`๐Ÿ’ฅ Unexpected error: ${error}`);
      throw new Error(`Unexpected error: ${error}`);
    }
  }
};

// ๐Ÿ›ก๏ธ Result type for safer error handling
type Result<T, E = Error> = {
  success: true;
  data: T;
} | {
  success: false;
  error: E;
};

// ๐ŸŽฏ Safe promise wrapper that never throws
const safePromise = async <T>(
  promise: Promise<T>
): Promise<Result<T>> => {
  try {
    const data = await promise;
    return { success: true, data };
  } catch (error) {
    return { 
      success: false, 
      error: error instanceof Error ? error : new Error(String(error))
    };
  }
};

๐Ÿ”„ Promise Composition and Chaining

Advanced patterns for combining promises:

// ๐Ÿ”— Promise chaining with type safety
interface ApiUser {
  id: string;
  username: string;
  email: string;
}

interface UserPosts {
  userId: string;
  posts: Array<{
    id: string;
    title: string;
    content: string;
    createdAt: Date;
  }>;
}

interface UserStats {
  userId: string;
  totalPosts: number;
  averageWordsPerPost: number;
  lastPostDate: Date | null;
}

// ๐ŸŽฏ Chained API calls with type preservation
const getUserWithPostsAndStats = async (userId: string): Promise<{
  user: ApiUser;
  posts: UserPosts;
  stats: UserStats;
}> => {
  // ๐Ÿ‘ค First, get the user
  const user = await fetchApiUser(userId);
  console.log(`๐Ÿ“‹ Fetched user: ${user.username}`);
  
  // ๐Ÿ“ Then get their posts
  const posts = await fetchUserPosts(user.id);
  console.log(`๐Ÿ“š Fetched ${posts.posts.length} posts`);
  
  // ๐Ÿ“Š Finally calculate stats
  const stats = await calculateUserStats(posts);
  console.log(`๐Ÿ“ˆ Calculated stats: ${stats.totalPosts} posts`);
  
  return { user, posts, stats };
};

// ๐ŸŽญ Mock API functions with proper typing
const fetchApiUser = (userId: string): Promise<ApiUser> => {
  return new Promise((resolve, reject) => {
    if (!userId) {
      reject(new Error("User ID required! ๐Ÿ“"));
      return;
    }
    
    setTimeout(() => {
      resolve({
        id: userId,
        username: `user_${userId}`,
        email: `${userId}@example.com`
      });
    }, 300);
  });
};

const fetchUserPosts = (userId: string): Promise<UserPosts> => {
  return new Promise((resolve) => {
    setTimeout(() => {
      const posts = Array.from({ length: 5 }, (_, i) => ({
        id: `post_${i + 1}`,
        title: `Post ${i + 1} by user ${userId}`,
        content: `This is the content of post ${i + 1}. Lorem ipsum dolor sit amet.`,
        createdAt: new Date(Date.now() - (i * 24 * 60 * 60 * 1000))
      }));
      
      resolve({ userId, posts });
    }, 500);
  });
};

const calculateUserStats = (userPosts: UserPosts): Promise<UserStats> => {
  return new Promise((resolve) => {
    setTimeout(() => {
      const { posts } = userPosts;
      const totalPosts = posts.length;
      
      const totalWords = posts.reduce((sum, post) => {
        return sum + post.content.split(' ').length;
      }, 0);
      
      const averageWordsPerPost = totalPosts > 0 ? totalWords / totalPosts : 0;
      const lastPostDate = posts.length > 0 ? posts[0].createdAt : null;
      
      resolve({
        userId: userPosts.userId,
        totalPosts,
        averageWordsPerPost: Math.round(averageWordsPerPost),
        lastPostDate
      });
    }, 200);
  });
};

๐Ÿ’ก Practical Examples

๐ŸŒ Example 1: Type-Safe API Client

Letโ€™s build a comprehensive, type-safe API client:

// ๐Ÿ“ก Generic API response types
interface ApiResponse<T> {
  success: boolean;
  data: T;
  message: string;
  timestamp: Date;
}

interface ApiError {
  success: false;
  error: {
    code: string;
    message: string;
    details?: Record<string, any>;
  };
  timestamp: Date;
}

// ๐ŸŽฏ HTTP methods enum
enum HttpMethod {
  GET = 'GET',
  POST = 'POST',
  PUT = 'PUT',
  DELETE = 'DELETE',
  PATCH = 'PATCH'
}

// ๐Ÿ”ง Request configuration interface
interface RequestConfig {
  method: HttpMethod;
  headers?: Record<string, string>;
  body?: any;
  timeout?: number;
  retries?: number;
}

// ๐Ÿš€ Type-safe API client class
class TypeSafeApiClient {
  private baseUrl: string;
  private defaultHeaders: Record<string, string>;
  private defaultTimeout: number = 5000;
  
  constructor(baseUrl: string, defaultHeaders: Record<string, string> = {}) {
    this.baseUrl = baseUrl.replace(/\/$/, ''); // Remove trailing slash
    this.defaultHeaders = {
      'Content-Type': 'application/json',
      ...defaultHeaders
    };
  }
  
  // ๐ŸŽฏ Generic request method with full type safety
  private async request<T>(
    endpoint: string, 
    config: RequestConfig
  ): Promise<ApiResponse<T>> {
    const url = `${this.baseUrl}${endpoint}`;
    const timeout = config.timeout || this.defaultTimeout;
    
    console.log(`๐Ÿ“ก ${config.method} ${url}`);
    
    try {
      // ๐Ÿ”ง Prepare request options
      const requestOptions: RequestInit = {
        method: config.method,
        headers: {
          ...this.defaultHeaders,
          ...config.headers
        }
      };
      
      // ๐Ÿ“ฆ Add body for non-GET requests
      if (config.body && config.method !== HttpMethod.GET) {
        requestOptions.body = JSON.stringify(config.body);
      }
      
      // โฐ Create timeout promise
      const timeoutPromise = new Promise<never>((_, reject) => {
        setTimeout(() => {
          reject(new Error(`Request timeout after ${timeout}ms โฐ`));
        }, timeout);
      });
      
      // ๐Ÿƒโ€โ™‚๏ธ Race between fetch and timeout
      const response = await Promise.race([
        fetch(url, requestOptions),
        timeoutPromise
      ]);
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText} ๐Ÿ“ก`);
      }
      
      const data = await response.json();
      
      const apiResponse: ApiResponse<T> = {
        success: true,
        data,
        message: 'Request successful',
        timestamp: new Date()
      };
      
      console.log(`โœ… ${config.method} ${url} - Success`);
      return apiResponse;
      
    } catch (error) {
      console.error(`โŒ ${config.method} ${url} - Failed:`, error);
      throw error;
    }
  }
  
  // ๐Ÿ” GET request with type safety
  async get<T>(endpoint: string, headers?: Record<string, string>): Promise<ApiResponse<T>> {
    return this.request<T>(endpoint, {
      method: HttpMethod.GET,
      headers
    });
  }
  
  // โž• POST request with typed body and response
  async post<TRequest, TResponse>(
    endpoint: string, 
    body: TRequest, 
    headers?: Record<string, string>
  ): Promise<ApiResponse<TResponse>> {
    return this.request<TResponse>(endpoint, {
      method: HttpMethod.POST,
      body,
      headers
    });
  }
  
  // ๐Ÿ”„ PUT request with full typing
  async put<TRequest, TResponse>(
    endpoint: string, 
    body: TRequest, 
    headers?: Record<string, string>
  ): Promise<ApiResponse<TResponse>> {
    return this.request<TResponse>(endpoint, {
      method: HttpMethod.PUT,
      body,
      headers
    });
  }
  
  // ๐Ÿ—‘๏ธ DELETE request
  async delete<T>(endpoint: string, headers?: Record<string, string>): Promise<ApiResponse<T>> {
    return this.request<T>(endpoint, {
      method: HttpMethod.DELETE,
      headers
    });
  }
  
  // ๐Ÿ›ก๏ธ Safe request wrapper that doesn't throw
  async safeRequest<T>(
    method: 'get' | 'post' | 'put' | 'delete',
    endpoint: string,
    body?: any
  ): Promise<Result<ApiResponse<T>, ApiError>> {
    try {
      let result: ApiResponse<T>;
      
      switch (method) {
        case 'get':
          result = await this.get<T>(endpoint);
          break;
        case 'post':
          result = await this.post<any, T>(endpoint, body);
          break;
        case 'put':
          result = await this.put<any, T>(endpoint, body);
          break;
        case 'delete':
          result = await this.delete<T>(endpoint);
          break;
      }
      
      return { success: true, data: result };
      
    } catch (error) {
      const apiError: ApiError = {
        success: false,
        error: {
          code: 'REQUEST_FAILED',
          message: error instanceof Error ? error.message : 'Unknown error',
          details: { method, endpoint, body }
        },
        timestamp: new Date()
      };
      
      return { success: false, error: apiError };
    }
  }
}

// ๐ŸŽฎ Usage example with specific types
interface BlogPost {
  id: string;
  title: string;
  content: string;
  author: string;
  publishedAt: Date;
  tags: string[];
}

interface CreatePostRequest {
  title: string;
  content: string;
  author: string;
  tags: string[];
}

// ๐Ÿ“š Blog API service using the typed client
class BlogApiService {
  private client: TypeSafeApiClient;
  
  constructor(apiUrl: string, authToken?: string) {
    const headers = authToken ? { 'Authorization': `Bearer ${authToken}` } : {};
    this.client = new TypeSafeApiClient(apiUrl, headers);
  }
  
  // ๐Ÿ“‹ Get all posts with type safety
  async getAllPosts(): Promise<BlogPost[]> {
    const response = await this.client.get<BlogPost[]>('/posts');
    return response.data;
  }
  
  // ๐Ÿ‘๏ธ Get single post by ID
  async getPost(id: string): Promise<BlogPost> {
    const response = await this.client.get<BlogPost>(`/posts/${id}`);
    return response.data;
  }
  
  // โž• Create new post
  async createPost(postData: CreatePostRequest): Promise<BlogPost> {
    const response = await this.client.post<CreatePostRequest, BlogPost>(
      '/posts', 
      postData
    );
    return response.data;
  }
  
  // ๐Ÿ”„ Update existing post
  async updatePost(id: string, postData: Partial<CreatePostRequest>): Promise<BlogPost> {
    const response = await this.client.put<Partial<CreatePostRequest>, BlogPost>(
      `/posts/${id}`, 
      postData
    );
    return response.data;
  }
  
  // ๐Ÿ—‘๏ธ Delete post
  async deletePost(id: string): Promise<{ deleted: boolean }> {
    const response = await this.client.delete<{ deleted: boolean }>(`/posts/${id}`);
    return response.data;
  }
}

๐ŸŽฏ Try it yourself: Add caching, request deduplication, and retry logic to the API client!

๐Ÿ“ Example 2: File Processing Pipeline

Letโ€™s create a type-safe file processing system:

// ๐Ÿ“„ File processing types
interface FileMetadata {
  name: string;
  size: number;
  type: string;
  lastModified: Date;
  path: string;
}

interface ProcessingResult<T> {
  file: FileMetadata;
  result: T;
  processingTime: number;
  success: boolean;
}

interface ImageProcessingOptions {
  width?: number;
  height?: number;
  quality?: number;
  format?: 'jpeg' | 'png' | 'webp';
}

interface ProcessedImage {
  originalSize: number;
  processedSize: number;
  dimensions: {
    width: number;
    height: number;
  };
  format: string;
  outputPath: string;
}

// ๐Ÿญ Type-safe file processing pipeline
class FileProcessingPipeline {
  private processingQueue: Map<string, Promise<any>> = new Map();
  
  // ๐Ÿ“Š Process multiple files with type safety
  async processFiles<T>(
    files: FileMetadata[],
    processor: (file: FileMetadata) => Promise<T>,
    options: {
      concurrency?: number;
      onProgress?: (completed: number, total: number) => void;
      onError?: (file: FileMetadata, error: Error) => void;
    } = {}
  ): Promise<ProcessingResult<T>[]> {
    const { concurrency = 3, onProgress, onError } = options;
    const results: ProcessingResult<T>[] = [];
    
    console.log(`๐Ÿญ Starting processing pipeline for ${files.length} files`);
    
    // ๐Ÿ”„ Process files in batches to control concurrency
    for (let i = 0; i < files.length; i += concurrency) {
      const batch = files.slice(i, i + concurrency);
      
      const batchPromises = batch.map(async (file): Promise<ProcessingResult<T>> => {
        const startTime = Date.now();
        
        try {
          console.log(`โš™๏ธ Processing: ${file.name}`);
          const result = await processor(file);
          const processingTime = Date.now() - startTime;
          
          console.log(`โœ… Completed: ${file.name} (${processingTime}ms)`);
          
          return {
            file,
            result,
            processingTime,
            success: true
          };
          
        } catch (error) {
          const processingTime = Date.now() - startTime;
          console.error(`โŒ Failed: ${file.name}`, error);
          
          if (onError) {
            onError(file, error instanceof Error ? error : new Error(String(error)));
          }
          
          return {
            file,
            result: null as any, // This will be filtered out
            processingTime,
            success: false
          };
        }
      });
      
      const batchResults = await Promise.allSettled(batchPromises);
      
      // ๐Ÿ“Š Extract successful results
      for (const result of batchResults) {
        if (result.status === 'fulfilled' && result.value.success) {
          results.push(result.value);
        }
      }
      
      // ๐Ÿ“ˆ Report progress
      if (onProgress) {
        onProgress(results.length, files.length);
      }
    }
    
    console.log(`๐ŸŽ‰ Pipeline completed: ${results.length}/${files.length} files processed`);
    return results;
  }
  
  // ๐Ÿ–ผ๏ธ Image-specific processing
  async processImages(
    imageFiles: FileMetadata[],
    options: ImageProcessingOptions = {}
  ): Promise<ProcessingResult<ProcessedImage>[]> {
    return this.processFiles(
      imageFiles.filter(file => file.type.startsWith('image/')),
      (file) => this.processImage(file, options),
      {
        concurrency: 2, // Images are resource-intensive
        onProgress: (completed, total) => {
          console.log(`๐Ÿ–ผ๏ธ Image processing: ${completed}/${total}`);
        },
        onError: (file, error) => {
          console.error(`โŒ Image processing failed for ${file.name}:`, error.message);
        }
      }
    );
  }
  
  // ๐ŸŽจ Single image processor
  private async processImage(
    file: FileMetadata, 
    options: ImageProcessingOptions
  ): Promise<ProcessedImage> {
    // ๐ŸŽญ Simulate image processing
    return new Promise((resolve, reject) => {
      if (file.size > 50 * 1024 * 1024) { // 50MB limit
        reject(new Error(`File too large: ${file.size} bytes ๐Ÿ“`));
        return;
      }
      
      setTimeout(() => {
        const processed: ProcessedImage = {
          originalSize: file.size,
          processedSize: Math.round(file.size * 0.7), // 30% compression
          dimensions: {
            width: options.width || 1920,
            height: options.height || 1080
          },
          format: options.format || 'jpeg',
          outputPath: `/processed/${file.name.replace(/\.[^.]+$/, `.${options.format || 'jpg'}`)}`
        };
        
        resolve(processed);
      }, Math.random() * 2000 + 500); // 0.5-2.5s processing time
    });
  }
  
  // ๐Ÿ“Š Get processing statistics
  async getProcessingStats(results: ProcessingResult<any>[]): Promise<{
    totalFiles: number;
    successfulFiles: number;
    failedFiles: number;
    averageProcessingTime: number;
    totalProcessingTime: number;
  }> {
    return new Promise((resolve) => {
      const successful = results.filter(r => r.success);
      const totalTime = results.reduce((sum, r) => sum + r.processingTime, 0);
      
      resolve({
        totalFiles: results.length,
        successfulFiles: successful.length,
        failedFiles: results.length - successful.length,
        averageProcessingTime: results.length > 0 ? Math.round(totalTime / results.length) : 0,
        totalProcessingTime: totalTime
      });
    });
  }
}

// ๐ŸŽฎ Usage example
const pipeline = new FileProcessingPipeline();

// ๐Ÿ“ Mock file metadata
const sampleFiles: FileMetadata[] = [
  {
    name: 'vacation-photo.jpg',
    size: 2.5 * 1024 * 1024, // 2.5MB
    type: 'image/jpeg',
    lastModified: new Date(),
    path: '/uploads/vacation-photo.jpg'
  },
  {
    name: 'profile-picture.png',
    size: 1.2 * 1024 * 1024, // 1.2MB
    type: 'image/png',
    lastModified: new Date(),
    path: '/uploads/profile-picture.png'
  },
  {
    name: 'presentation.pptx',
    size: 15 * 1024 * 1024, // 15MB
    type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
    lastModified: new Date(),
    path: '/uploads/presentation.pptx'
  }
];

// ๐Ÿš€ Process images with type safety
async function runImageProcessing(): Promise<void> {
  try {
    const results = await pipeline.processImages(sampleFiles, {
      width: 1200,
      height: 800,
      quality: 85,
      format: 'jpeg'
    });
    
    console.log(`๐ŸŽฏ Processed ${results.length} images successfully`);
    
    const stats = await pipeline.getProcessingStats(results);
    console.log('๐Ÿ“Š Processing Statistics:', stats);
    
  } catch (error) {
    console.error('โŒ Pipeline failed:', error);
  }
}

๐Ÿš€ Advanced Promise Patterns

๐Ÿง™โ€โ™‚๏ธ Promise Utilities and Helpers

Advanced utilities for promise composition:

// ๐ŸŽฏ Advanced Promise utility class
class PromiseUtils {
  // โฐ Timeout wrapper for any promise
  static withTimeout<T>(
    promise: Promise<T>, 
    timeoutMs: number, 
    timeoutMessage?: string
  ): Promise<T> {
    const timeoutPromise = new Promise<never>((_, reject) => {
      setTimeout(() => {
        reject(new Error(timeoutMessage || `Operation timed out after ${timeoutMs}ms โฐ`));
      }, timeoutMs);
    });
    
    return Promise.race([promise, timeoutPromise]);
  }
  
  // ๐Ÿ”„ Retry failed promises with exponential backoff
  static async retry<T>(
    promiseFactory: () => Promise<T>,
    options: {
      maxAttempts?: number;
      baseDelay?: number;
      maxDelay?: number;
      backoffFactor?: number;
      shouldRetry?: (error: Error) => boolean;
    } = {}
  ): Promise<T> {
    const {
      maxAttempts = 3,
      baseDelay = 1000,
      maxDelay = 10000,
      backoffFactor = 2,
      shouldRetry = () => true
    } = options;
    
    let lastError: Error;
    
    for (let attempt = 1; attempt <= maxAttempts; attempt++) {
      try {
        console.log(`๐Ÿ”„ Attempt ${attempt}/${maxAttempts}`);
        return await promiseFactory();
      } catch (error) {
        lastError = error instanceof Error ? error : new Error(String(error));
        
        if (attempt === maxAttempts || !shouldRetry(lastError)) {
          break;
        }
        
        // ๐Ÿ“ˆ Calculate delay with exponential backoff
        const delay = Math.min(
          baseDelay * Math.pow(backoffFactor, attempt - 1),
          maxDelay
        );
        
        console.log(`โณ Waiting ${delay}ms before retry...`);
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
    
    throw lastError!;
  }
  
  // ๐ŸŽ๏ธ Parallel promise execution with concurrency limit
  static async parallel<T>(
    tasks: (() => Promise<T>)[],
    concurrency: number = 5
  ): Promise<T[]> {
    const results: T[] = [];
    const executing: Promise<void>[] = [];
    
    for (const task of tasks) {
      const promise = task().then(result => {
        results.push(result);
      });
      
      executing.push(promise);
      
      if (executing.length >= concurrency) {
        await Promise.race(executing);
        // Remove completed promises
        executing.splice(0, executing.length);
      }
    }
    
    // Wait for remaining promises
    await Promise.all(executing);
    return results;
  }
  
  // ๐Ÿ”„ Chain promises in sequence
  static async sequence<T>(
    tasks: (() => Promise<T>)[]
  ): Promise<T[]> {
    const results: T[] = [];
    
    for (const task of tasks) {
      const result = await task();
      results.push(result);
    }
    
    return results;
  }
  
  // ๐ŸŽฐ Promise with random delay (useful for testing)
  static delay<T>(value: T, minMs: number = 0, maxMs?: number): Promise<T> {
    const delay = maxMs ? Math.random() * (maxMs - minMs) + minMs : minMs;
    
    return new Promise(resolve => {
      setTimeout(() => resolve(value), delay);
    });
  }
  
  // ๐ŸŽฏ Create a cancellable promise
  static cancellable<T>(promise: Promise<T>): {
    promise: Promise<T>;
    cancel: () => void;
  } {
    let isCancelled = false;
    let rejectFn: (reason?: any) => void;
    
    const cancellablePromise = new Promise<T>((resolve, reject) => {
      rejectFn = reject;
      
      promise
        .then(value => {
          if (!isCancelled) {
            resolve(value);
          }
        })
        .catch(error => {
          if (!isCancelled) {
            reject(error);
          }
        });
    });
    
    return {
      promise: cancellablePromise,
      cancel: () => {
        isCancelled = true;
        rejectFn(new Error('Promise was cancelled ๐Ÿšซ'));
      }
    };
  }
}

// ๐ŸŽฎ Example usage of advanced utilities
async function demonstrateAdvancedPatterns(): Promise<void> {
  try {
    // โฐ Timeout example
    const quickData = await PromiseUtils.withTimeout(
      fetchDataWithDelay(500),
      1000,
      "Data fetch took too long! โฐ"
    );
    console.log('โšก Quick data:', quickData);
    
    // ๐Ÿ”„ Retry example
    const retryData = await PromiseUtils.retry(
      () => unreliableApiCall(),
      {
        maxAttempts: 3,
        baseDelay: 500,
        shouldRetry: (error) => !error.message.includes('validation')
      }
    );
    console.log('๐Ÿ”„ Retry data:', retryData);
    
    // ๐ŸŽ๏ธ Parallel execution
    const parallelTasks = Array.from({ length: 10 }, (_, i) => 
      () => PromiseUtils.delay(`Task ${i + 1}`, 100, 1000)
    );
    
    const parallelResults = await PromiseUtils.parallel(parallelTasks, 3);
    console.log('๐ŸŽ๏ธ Parallel results:', parallelResults);
    
  } catch (error) {
    console.error('โŒ Advanced patterns demo failed:', error);
  }
}

// ๐ŸŽญ Mock functions for demonstration
async function fetchDataWithDelay(delayMs: number): Promise<string> {
  await new Promise(resolve => setTimeout(resolve, delayMs));
  return `Data fetched after ${delayMs}ms delay ๐Ÿ“Š`;
}

async function unreliableApiCall(): Promise<string> {
  if (Math.random() < 0.7) {
    throw new Error('Network error ๐ŸŒ');
  }
  return 'API call successful! โœ…';
}

๐Ÿ—๏ธ Promise-Based State Management

Creating a type-safe state management system with promises:

// ๐Ÿ“Š State management types
interface StateChange<T> {
  previous: T;
  current: T;
  timestamp: Date;
  action: string;
}

interface StateSubscription<T> {
  id: string;
  callback: (change: StateChange<T>) => void;
  filter?: (change: StateChange<T>) => boolean;
}

// ๐ŸŽฏ Type-safe promise-based state manager
class PromiseStateManager<T> {
  private state: T;
  private subscriptions: Map<string, StateSubscription<T>> = new Map();
  private pendingUpdates: Map<string, Promise<T>> = new Map();
  private history: StateChange<T>[] = [];
  private maxHistorySize: number = 100;
  
  constructor(initialState: T) {
    this.state = initialState;
    console.log('๐ŸŽฏ State manager initialized');
  }
  
  // ๐Ÿ“– Get current state
  getState(): T {
    return { ...this.state } as T; // Return a copy to prevent mutations
  }
  
  // ๐Ÿ”„ Update state with a promise-based updater
  async updateState(
    updater: (currentState: T) => Promise<T> | T,
    actionName: string = 'UPDATE'
  ): Promise<T> {
    const updateId = `${actionName}_${Date.now()}_${Math.random()}`;
    
    // ๐Ÿ”’ Prevent concurrent updates of the same action
    if (this.pendingUpdates.has(actionName)) {
      console.log(`โณ Waiting for pending ${actionName} to complete...`);
      await this.pendingUpdates.get(actionName);
    }
    
    try {
      console.log(`๐Ÿ”„ Starting state update: ${actionName}`);
      
      const updatePromise = this.performUpdate(updater, actionName);
      this.pendingUpdates.set(actionName, updatePromise);
      
      const newState = await updatePromise;
      return newState;
      
    } finally {
      this.pendingUpdates.delete(actionName);
    }
  }
  
  // ๐ŸŽญ Perform the actual state update
  private async performUpdate(
    updater: (currentState: T) => Promise<T> | T,
    actionName: string
  ): Promise<T> {
    const previousState = this.getState();
    
    try {
      // ๐Ÿš€ Apply the updater function
      const result = updater(previousState);
      const newState = await Promise.resolve(result);
      
      // ๐Ÿ“ Record the state change
      const change: StateChange<T> = {
        previous: previousState,
        current: newState,
        timestamp: new Date(),
        action: actionName
      };
      
      // ๐Ÿ’พ Update internal state
      this.state = newState;
      this.addToHistory(change);
      
      // ๐Ÿ“ข Notify subscribers
      await this.notifySubscribers(change);
      
      console.log(`โœ… State updated: ${actionName}`);
      return newState;
      
    } catch (error) {
      console.error(`โŒ State update failed: ${actionName}`, error);
      throw error;
    }
  }
  
  // ๐Ÿ“ Add change to history
  private addToHistory(change: StateChange<T>): void {
    this.history.push(change);
    
    // ๐Ÿงน Keep history size manageable
    if (this.history.length > this.maxHistorySize) {
      this.history = this.history.slice(-this.maxHistorySize);
    }
  }
  
  // ๐Ÿ“ข Notify all subscribers about state changes
  private async notifySubscribers(change: StateChange<T>): Promise<void> {
    const notifications = Array.from(this.subscriptions.values())
      .filter(sub => !sub.filter || sub.filter(change))
      .map(async (sub) => {
        try {
          await Promise.resolve(sub.callback(change));
        } catch (error) {
          console.error(`โŒ Subscriber ${sub.id} error:`, error);
        }
      });
    
    await Promise.all(notifications);
  }
  
  // ๐Ÿ‘‚ Subscribe to state changes
  subscribe(
    callback: (change: StateChange<T>) => void,
    filter?: (change: StateChange<T>) => boolean
  ): string {
    const id = `sub_${Date.now()}_${Math.random()}`;
    
    this.subscriptions.set(id, {
      id,
      callback,
      filter
    });
    
    console.log(`๐Ÿ‘‚ Subscriber ${id} added`);
    return id;
  }
  
  // ๐Ÿ‘‹ Unsubscribe from state changes
  unsubscribe(subscriptionId: string): boolean {
    const removed = this.subscriptions.delete(subscriptionId);
    if (removed) {
      console.log(`๐Ÿ‘‹ Subscriber ${subscriptionId} removed`);
    }
    return removed;
  }
  
  // ๐Ÿ“œ Get state history
  getHistory(limit?: number): StateChange<T>[] {
    return limit ? this.history.slice(-limit) : [...this.history];
  }
  
  // โช Rollback to a previous state
  async rollback(steps: number = 1): Promise<T> {
    if (this.history.length < steps) {
      throw new Error(`Cannot rollback ${steps} steps, only ${this.history.length} changes in history`);
    }
    
    const targetState = this.history[this.history.length - steps - 1]?.current || this.history[0]?.previous;
    
    if (!targetState) {
      throw new Error('No target state found for rollback');
    }
    
    return this.updateState(() => targetState, `ROLLBACK_${steps}`);
  }
}

// ๐ŸŽฎ Example usage with a shopping cart
interface CartItem {
  id: string;
  name: string;
  price: number;
  quantity: number;
}

interface ShoppingCartState {
  items: CartItem[];
  total: number;
  discountCode?: string;
  discountAmount: number;
}

// ๐Ÿ›’ Shopping cart with promise-based state management
class ShoppingCart {
  private stateManager: PromiseStateManager<ShoppingCartState>;
  
  constructor() {
    const initialState: ShoppingCartState = {
      items: [],
      total: 0,
      discountAmount: 0
    };
    
    this.stateManager = new PromiseStateManager(initialState);
    
    // ๐Ÿ‘‚ Subscribe to total changes
    this.stateManager.subscribe(
      (change) => {
        console.log(`๐Ÿ’ฐ Cart total: $${change.current.total.toFixed(2)}`);
      },
      (change) => change.previous.total !== change.current.total
    );
  }
  
  // โž• Add item to cart with async validation
  async addItem(item: Omit<CartItem, 'id'>): Promise<ShoppingCartState> {
    return this.stateManager.updateState(async (state) => {
      // ๐Ÿ” Simulate async inventory check
      await this.checkInventory(item.name);
      
      const newItem: CartItem = {
        ...item,
        id: `item_${Date.now()}_${Math.random()}`
      };
      
      const existingItemIndex = state.items.findIndex(i => i.name === item.name);
      
      let newItems: CartItem[];
      if (existingItemIndex >= 0) {
        // ๐Ÿ”„ Update quantity of existing item
        newItems = [...state.items];
        newItems[existingItemIndex] = {
          ...newItems[existingItemIndex],
          quantity: newItems[existingItemIndex].quantity + item.quantity
        };
      } else {
        // โž• Add new item
        newItems = [...state.items, newItem];
      }
      
      const newTotal = this.calculateTotal(newItems, state.discountAmount);
      
      return {
        ...state,
        items: newItems,
        total: newTotal
      };
    }, 'ADD_ITEM');
  }
  
  // ๐Ÿท๏ธ Apply discount code with async validation
  async applyDiscountCode(code: string): Promise<ShoppingCartState> {
    return this.stateManager.updateState(async (state) => {
      // ๐Ÿ” Simulate async discount validation
      const discount = await this.validateDiscountCode(code);
      
      const discountAmount = state.total * discount.percentage;
      const newTotal = this.calculateTotal(state.items, discountAmount);
      
      return {
        ...state,
        discountCode: code,
        discountAmount,
        total: newTotal
      };
    }, 'APPLY_DISCOUNT');
  }
  
  // ๐Ÿงฎ Calculate total
  private calculateTotal(items: CartItem[], discountAmount: number = 0): number {
    const subtotal = items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
    return Math.max(0, subtotal - discountAmount);
  }
  
  // ๐Ÿ” Mock inventory check
  private async checkInventory(itemName: string): Promise<void> {
    await new Promise(resolve => setTimeout(resolve, 200));
    
    if (itemName.toLowerCase().includes('outofstock')) {
      throw new Error(`${itemName} is out of stock! ๐Ÿ“ฆ`);
    }
  }
  
  // ๐Ÿท๏ธ Mock discount validation
  private async validateDiscountCode(code: string): Promise<{ percentage: number }> {
    await new Promise(resolve => setTimeout(resolve, 300));
    
    const validCodes: Record<string, number> = {
      'SAVE10': 0.10,
      'SAVE20': 0.20,
      'WELCOME': 0.15
    };
    
    if (!validCodes[code]) {
      throw new Error(`Invalid discount code: ${code} ๐Ÿท๏ธ`);
    }
    
    return { percentage: validCodes[code] };
  }
  
  // ๐Ÿ“Š Get current cart state
  getState(): ShoppingCartState {
    return this.stateManager.getState();
  }
  
  // ๐Ÿ‘‚ Subscribe to cart changes
  onCartChange(callback: (change: StateChange<ShoppingCartState>) => void): string {
    return this.stateManager.subscribe(callback);
  }
}

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: Losing Type Information in Promise Chains

// โŒ Wrong way - losing types in complex chains
async function fetchUserDataBad(): Promise<any> {
  const response = await fetch('/api/user');
  const data = await response.json(); // ๐Ÿ’ฅ Returns 'any'
  const processed = await processData(data); // ๐Ÿ’ฅ Still 'any'
  return processed; // ๐Ÿ’ฅ No type safety!
}

// Using the result loses all type information
const userData = await fetchUserDataBad();
// userData. <-- No autocomplete, no type safety
// โœ… Correct way - maintain types throughout the chain
interface UserApiResponse {
  user: {
    id: string;
    name: string;
    email: string;
  };
  metadata: {
    lastLogin: string;
    preferences: Record<string, any>;
  };
}

interface ProcessedUserData {
  id: string;
  name: string;
  email: string;
  lastLogin: Date;
  preferences: Record<string, any>;
}

async function fetchUserDataGood(): Promise<ProcessedUserData> {
  // ๐ŸŽฏ Type the response explicitly
  const response = await fetch('/api/user');
  const data: UserApiResponse = await response.json();
  
  // ๐Ÿ”„ Type-safe processing
  const processed: ProcessedUserData = await processUserData(data);
  return processed;
}

async function processUserData(data: UserApiResponse): Promise<ProcessedUserData> {
  return {
    id: data.user.id,
    name: data.user.name,
    email: data.user.email,
    lastLogin: new Date(data.metadata.lastLogin),
    preferences: data.metadata.preferences
  };
}

// โœ… Now we have full type safety!
const userData = await fetchUserDataGood();
console.log(userData.name); // ๐ŸŽฏ Full autocomplete and type checking

๐Ÿคฏ Pitfall 2: Not Handling Promise Rejection Types

// โŒ Wrong way - not typing errors properly
async function riskyOperation(): Promise<string> {
  try {
    const result = await someAsyncOperation();
    return result;
  } catch (error) {
    // ๐Ÿ’ฅ 'error' is unknown type - no type safety!
    console.log(error.message); // ๐Ÿšซ TypeScript error
    throw error;
  }
}
// โœ… Correct way - proper error typing
interface ApiError {
  code: string;
  message: string;
  statusCode: number;
}

class NetworkError extends Error {
  constructor(public statusCode: number, message: string) {
    super(message);
    this.name = 'NetworkError';
  }
}

async function safeOperation(): Promise<string> {
  try {
    const result = await someAsyncOperation();
    return result;
  } catch (error) {
    // ๐ŸŽฏ Type-safe error handling
    if (error instanceof NetworkError) {
      console.log(`Network error ${error.statusCode}: ${error.message}`);
      throw error;
    } else if (error && typeof error === 'object' && 'code' in error) {
      const apiError = error as ApiError;
      console.log(`API error ${apiError.code}: ${apiError.message}`);
      throw new Error(`API Error: ${apiError.message}`);
    } else {
      console.log(`Unknown error: ${String(error)}`);
      throw new Error(`Unknown error: ${String(error)}`);
    }
  }
}

// ๐Ÿ›ก๏ธ Even better - use Result types for safer error handling
type OperationResult<T> = 
  | { success: true; data: T }
  | { success: false; error: string; errorType: 'network' | 'api' | 'unknown' };

async function safestOperation(): Promise<OperationResult<string>> {
  try {
    const result = await someAsyncOperation();
    return { success: true, data: result };
  } catch (error) {
    if (error instanceof NetworkError) {
      return { 
        success: false, 
        error: error.message, 
        errorType: 'network' 
      };
    } else {
      return { 
        success: false, 
        error: String(error), 
        errorType: 'unknown' 
      };
    }
  }
}

๐Ÿ”ฅ Pitfall 3: Promise Constructor Anti-patterns

// โŒ Wrong way - unnecessary Promise constructor
async function unnecessaryPromiseConstructor(): Promise<string> {
  return new Promise(async (resolve, reject) => {
    try {
      const data = await fetch('/api/data');
      const result = await data.json();
      resolve(result); // ๐Ÿ’ฅ This is redundant!
    } catch (error) {
      reject(error); // ๐Ÿ’ฅ This is also redundant!
    }
  });
}

// โŒ Also wrong - mixing callbacks with async/await
function mixedPatterns(): Promise<string> {
  return new Promise((resolve, reject) => {
    fetch('/api/data')
      .then(async response => {
        const data = await response.json(); // ๐Ÿ’ฅ Mixing patterns
        resolve(data);
      })
      .catch(reject);
  });
}
// โœ… Correct way - use async/await directly
async function cleanAsyncFunction(): Promise<string> {
  const response = await fetch('/api/data');
  const data = await response.json();
  return data; // โœ… Clean and simple!
}

// โœ… Use Promise constructor only when wrapping callbacks
function wrapCallbackAPI(): Promise<string> {
  return new Promise((resolve, reject) => {
    // ๐ŸŽฏ Only use Promise constructor for callback APIs
    oldCallbackFunction((error: Error | null, result: string) => {
      if (error) {
        reject(error);
      } else {
        resolve(result);
      }
    });
  });
}

// ๐ŸŽฏ Helper function to promisify callback APIs
function promisify<T>(
  fn: (callback: (error: Error | null, result: T) => void) => void
): Promise<T> {
  return new Promise((resolve, reject) => {
    fn((error, result) => {
      if (error) {
        reject(error);
      } else {
        resolve(result);
      }
    });
  });
}

// โœ… Clean usage of promisify
const promisifiedFunction = () => promisify<string>(oldCallbackFunction);

๐Ÿ› ๏ธ Best Practices

  1. ๐ŸŽฏ Always Type Promise Results: Use Promise<T> with specific types
  2. ๐Ÿ“ Handle Errors Explicitly: Donโ€™t let errors be unknown type
  3. ๐Ÿ›ก๏ธ Use Result Types: Consider Result<T, E> pattern for safer error handling
  4. ๐ŸŽจ Avoid Promise Constructor: Use async/await instead of new Promise
  5. โœจ Compose Small Functions: Build complex async flows from simple parts
  6. ๐Ÿ”’ Use Cancellation: Implement cancellation for long-running operations
  7. ๐Ÿ“ฆ Batch Related Operations: Use Promise.all for independent parallel tasks

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Build a Type-Safe Data Synchronization System

Create a robust data sync system that handles multiple data sources:

๐Ÿ“‹ Requirements:

  • ๐Ÿ”„ Sync data between local storage, remote API, and cache
  • ๐Ÿ›ก๏ธ Full type safety for all data operations
  • โš ๏ธ Comprehensive error handling with typed errors
  • ๐Ÿ“Š Progress tracking and status reporting
  • ๐ŸŽฏ Conflict resolution for concurrent updates
  • ๐Ÿ”„ Retry logic with exponential backoff
  • ๐ŸŽจ Real-time status updates with promises!

๐Ÿš€ Bonus Points:

  • Add offline detection and queue management
  • Implement data versioning and merge strategies
  • Create performance metrics and monitoring

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
// ๐ŸŽฏ Data synchronization system with full type safety!

// ๐Ÿ“Š Core sync types
interface SyncableData {
  id: string;
  version: number;
  updatedAt: Date;
  data: Record<string, any>;
}

interface SyncStatus {
  status: 'idle' | 'syncing' | 'error' | 'completed';
  progress: number;
  message: string;
  lastSync?: Date;
  errors: SyncError[];
}

interface SyncError {
  source: 'local' | 'remote' | 'cache';
  operation: 'read' | 'write' | 'delete';
  error: string;
  timestamp: Date;
  dataId?: string;
}

interface SyncResult<T extends SyncableData> {
  success: boolean;
  syncedItems: T[];
  conflicts: ConflictResolution<T>[];
  errors: SyncError[];
  duration: number;
}

interface ConflictResolution<T extends SyncableData> {
  localData: T;
  remoteData: T;
  resolution: 'local' | 'remote' | 'merged';
  resolvedData: T;
}

// ๐Ÿ—๏ธ Type-safe data synchronizer
class DataSynchronizer<T extends SyncableData> {
  private syncStatus: SyncStatus = {
    status: 'idle',
    progress: 0,
    message: 'Ready to sync',
    errors: []
  };
  
  private statusSubscribers: ((status: SyncStatus) => void)[] = [];
  
  constructor(
    private localStore: DataStore<T>,
    private remoteStore: DataStore<T>,
    private cacheStore: DataStore<T>
  ) {}
  
  // ๐Ÿš€ Main synchronization method
  async synchronize(): Promise<SyncResult<T>> {
    const startTime = Date.now();
    
    try {
      this.updateStatus('syncing', 0, 'Starting synchronization...');
      
      // ๐Ÿ“‹ Phase 1: Fetch data from all sources
      const [localData, remoteData, cacheData] = await Promise.all([
        this.safeOperation(() => this.localStore.getAll(), 'local', 'read'),
        this.safeOperation(() => this.remoteStore.getAll(), 'remote', 'read'),
        this.safeOperation(() => this.cacheStore.getAll(), 'cache', 'read')
      ]);
      
      this.updateStatus('syncing', 25, 'Data fetched, analyzing differences...');
      
      // ๐Ÿ” Phase 2: Detect conflicts and differences
      const conflicts = this.detectConflicts(localData, remoteData);
      const resolvedConflicts = await this.resolveConflicts(conflicts);
      
      this.updateStatus('syncing', 50, 'Conflicts resolved, syncing changes...');
      
      // ๐Ÿ”„ Phase 3: Apply resolved changes
      const syncedItems = await this.applySyncChanges(resolvedConflicts, localData, remoteData);
      
      this.updateStatus('syncing', 75, 'Updating cache...');
      
      // ๐Ÿ’พ Phase 4: Update cache with synced data
      await this.updateCache(syncedItems);
      
      this.updateStatus('completed', 100, 'Synchronization completed successfully!');
      
      const result: SyncResult<T> = {
        success: true,
        syncedItems,
        conflicts: resolvedConflicts,
        errors: this.syncStatus.errors,
        duration: Date.now() - startTime
      };
      
      console.log(`โœ… Sync completed in ${result.duration}ms`);
      return result;
      
    } catch (error) {
      this.updateStatus('error', this.syncStatus.progress, `Sync failed: ${error}`);
      
      return {
        success: false,
        syncedItems: [],
        conflicts: [],
        errors: this.syncStatus.errors,
        duration: Date.now() - startTime
      };
    }
  }
  
  // ๐Ÿ›ก๏ธ Safe operation wrapper
  private async safeOperation<R>(
    operation: () => Promise<R>,
    source: SyncError['source'],
    operationType: SyncError['operation']
  ): Promise<R> {
    try {
      return await PromiseUtils.withTimeout(operation(), 5000);
    } catch (error) {
      const syncError: SyncError = {
        source,
        operation: operationType,
        error: error instanceof Error ? error.message : String(error),
        timestamp: new Date()
      };
      
      this.syncStatus.errors.push(syncError);
      console.error(`โŒ ${source} ${operationType} failed:`, error);
      throw error;
    }
  }
  
  // ๐Ÿ” Detect conflicts between local and remote data
  private detectConflicts(localData: T[], remoteData: T[]): ConflictResolution<T>[] {
    const conflicts: ConflictResolution<T>[] = [];
    const remoteMap = new Map(remoteData.map(item => [item.id, item]));
    
    for (const localItem of localData) {
      const remoteItem = remoteMap.get(localItem.id);
      
      if (remoteItem && localItem.version !== remoteItem.version) {
        // ๐ŸฅŠ Version conflict detected
        conflicts.push({
          localData: localItem,
          remoteData: remoteItem,
          resolution: 'local', // Default, will be resolved
          resolvedData: localItem // Temporary
        });
      }
    }
    
    console.log(`๐Ÿ” Detected ${conflicts.length} conflicts`);
    return conflicts;
  }
  
  // ๐Ÿค Resolve conflicts using various strategies
  private async resolveConflicts(conflicts: ConflictResolution<T>[]): Promise<ConflictResolution<T>[]> {
    const resolved: ConflictResolution<T>[] = [];
    
    for (const conflict of conflicts) {
      const resolution = await this.resolveConflict(conflict);
      resolved.push(resolution);
    }
    
    return resolved;
  }
  
  // ๐ŸŽฏ Individual conflict resolution
  private async resolveConflict(conflict: ConflictResolution<T>): Promise<ConflictResolution<T>> {
    const { localData, remoteData } = conflict;
    
    // ๐Ÿ“… Timestamp-based resolution (latest wins)
    if (localData.updatedAt > remoteData.updatedAt) {
      return {
        ...conflict,
        resolution: 'local',
        resolvedData: { ...localData, version: Math.max(localData.version, remoteData.version) + 1 }
      };
    } else if (remoteData.updatedAt > localData.updatedAt) {
      return {
        ...conflict,
        resolution: 'remote',
        resolvedData: { ...remoteData, version: Math.max(localData.version, remoteData.version) + 1 }
      };
    } else {
      // ๐Ÿ”„ Same timestamp - merge data
      const mergedData: T = {
        ...localData,
        ...remoteData,
        id: localData.id,
        version: Math.max(localData.version, remoteData.version) + 1,
        updatedAt: new Date(),
        data: { ...localData.data, ...remoteData.data }
      };
      
      return {
        ...conflict,
        resolution: 'merged',
        resolvedData: mergedData
      };
    }
  }
  
  // ๐Ÿ“ Apply sync changes to stores
  private async applySyncChanges(
    resolvedConflicts: ConflictResolution<T>[],
    localData: T[],
    remoteData: T[]
  ): Promise<T[]> {
    const syncedItems: T[] = [];
    
    // ๐Ÿ”„ Apply conflict resolutions
    for (const conflict of resolvedConflicts) {
      const { resolvedData, resolution } = conflict;
      
      try {
        // ๐Ÿ’พ Update both stores with resolved data
        await Promise.all([
          this.localStore.update(resolvedData),
          this.remoteStore.update(resolvedData)
        ]);
        
        syncedItems.push(resolvedData);
        console.log(`๐Ÿ”„ Resolved conflict for ${resolvedData.id} using ${resolution} strategy`);
        
      } catch (error) {
        console.error(`โŒ Failed to apply conflict resolution for ${resolvedData.id}:`, error);
      }
    }
    
    return syncedItems;
  }
  
  // ๐Ÿ’พ Update cache with synced data
  private async updateCache(syncedItems: T[]): Promise<void> {
    try {
      await Promise.all(
        syncedItems.map(item => this.cacheStore.update(item))
      );
      console.log(`๐Ÿ’พ Updated cache with ${syncedItems.length} items`);
    } catch (error) {
      console.error('โŒ Cache update failed:', error);
    }
  }
  
  // ๐Ÿ“Š Update sync status and notify subscribers
  private updateStatus(status: SyncStatus['status'], progress: number, message: string): void {
    this.syncStatus = {
      ...this.syncStatus,
      status,
      progress,
      message,
      lastSync: status === 'completed' ? new Date() : this.syncStatus.lastSync
    };
    
    console.log(`๐Ÿ“Š Sync ${status}: ${progress}% - ${message}`);
    this.statusSubscribers.forEach(callback => callback(this.syncStatus));
  }
  
  // ๐Ÿ‘‚ Subscribe to status updates
  onStatusUpdate(callback: (status: SyncStatus) => void): () => void {
    this.statusSubscribers.push(callback);
    
    return () => {
      const index = this.statusSubscribers.indexOf(callback);
      if (index > -1) {
        this.statusSubscribers.splice(index, 1);
      }
    };
  }
  
  // ๐Ÿ“‹ Get current sync status
  getStatus(): SyncStatus {
    return { ...this.syncStatus };
  }
}

// ๐Ÿ“ฆ Mock data store interface
interface DataStore<T extends SyncableData> {
  getAll(): Promise<T[]>;
  update(item: T): Promise<void>;
  delete(id: string): Promise<void>;
}

// ๐ŸŽญ Mock implementation for demonstration
class MockDataStore<T extends SyncableData> implements DataStore<T> {
  private data: Map<string, T> = new Map();
  
  constructor(private storeName: string, initialData: T[] = []) {
    initialData.forEach(item => this.data.set(item.id, item));
  }
  
  async getAll(): Promise<T[]> {
    await PromiseUtils.delay(null, 100, 300); // Simulate network delay
    return Array.from(this.data.values());
  }
  
  async update(item: T): Promise<void> {
    await PromiseUtils.delay(null, 50, 150);
    this.data.set(item.id, item);
    console.log(`๐Ÿ“ฆ ${this.storeName}: Updated ${item.id}`);
  }
  
  async delete(id: string): Promise<void> {
    await PromiseUtils.delay(null, 50, 100);
    this.data.delete(id);
    console.log(`๐Ÿ—‘๏ธ ${this.storeName}: Deleted ${id}`);
  }
}

// ๐ŸŽฎ Example usage
interface UserData extends SyncableData {
  data: {
    name: string;
    email: string;
    preferences: Record<string, any>;
  };
}

async function demonstrateDataSync(): Promise<void> {
  // ๐Ÿ“ฆ Create mock data stores
  const localStore = new MockDataStore<UserData>('Local', [
    {
      id: 'user1',
      version: 1,
      updatedAt: new Date(Date.now() - 60000), // 1 minute ago
      data: { name: 'Alice', email: '[email protected]', preferences: { theme: 'dark' } }
    }
  ]);
  
  const remoteStore = new MockDataStore<UserData>('Remote', [
    {
      id: 'user1',
      version: 2,
      updatedAt: new Date(), // Now
      data: { name: 'Alice Updated', email: '[email protected]', preferences: { theme: 'light' } }
    }
  ]);
  
  const cacheStore = new MockDataStore<UserData>('Cache');
  
  // ๐Ÿš€ Create synchronizer
  const synchronizer = new DataSynchronizer(localStore, remoteStore, cacheStore);
  
  // ๐Ÿ‘‚ Subscribe to status updates
  const unsubscribe = synchronizer.onStatusUpdate((status) => {
    console.log(`๐Ÿ“Š Status: ${status.status} (${status.progress}%) - ${status.message}`);
  });
  
  try {
    // ๐Ÿ”„ Perform synchronization
    const result = await synchronizer.synchronize();
    
    console.log('\n๐Ÿ“‹ Synchronization Result:');
    console.log(`โœ… Success: ${result.success}`);
    console.log(`๐Ÿ”„ Synced items: ${result.syncedItems.length}`);
    console.log(`๐ŸฅŠ Conflicts: ${result.conflicts.length}`);
    console.log(`โŒ Errors: ${result.errors.length}`);
    console.log(`โฑ๏ธ Duration: ${result.duration}ms`);
    
  } finally {
    unsubscribe();
  }
}

๐ŸŽ“ Key Takeaways

Youโ€™ve mastered type-safe asynchronous programming in TypeScript! Hereโ€™s what you can now do:

  • โœ… Create bulletproof Promise types with complete type safety ๐Ÿ’ช
  • โœ… Handle async errors gracefully with typed error handling ๐Ÿ›ก๏ธ
  • โœ… Build complex async workflows that are impossible to break ๐ŸŽฏ
  • โœ… Compose promises efficiently with advanced patterns and utilities ๐Ÿ”ง
  • โœ… Debug async code confidently with full type information ๐Ÿš€

Remember: Type-safe promises transform unreliable async operations into predictable, maintainable systems. Your future self will thank you! ๐Ÿค

๐Ÿค Next Steps

Congratulations! ๐ŸŽ‰ Youโ€™ve mastered Promises in TypeScript!

Hereโ€™s what to do next:

  1. ๐Ÿ’ป Practice with the data synchronization exercise above
  2. ๐Ÿ—๏ธ Refactor existing async code to use proper Promise types
  3. ๐Ÿ“š Move on to our next tutorial: Jest with TypeScript: Test Runner Setup
  4. ๐ŸŒŸ Share your type-safe async patterns with the community!

Remember: Every typed promise is a step toward bulletproof applications. Keep building the future! ๐Ÿš€


Happy async coding! ๐ŸŽ‰โšกโœจ