+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 113 of 355

๐Ÿ”ฎ Async Functions: Return Types and Behavior in TypeScript

Master async function mechanics, return types, and execution patterns with practical examples, error handling, and performance patterns ๐Ÿš€

๐Ÿš€Intermediate
17 min read

Prerequisites

  • Understanding of Promise fundamentals ๐Ÿ“
  • Basic async/await syntax knowledge โšก
  • TypeScript function types experience ๐Ÿ’ป

What you'll learn

  • Master async function return types and behavior ๐ŸŽฏ
  • Understand async function execution patterns ๐Ÿ—๏ธ
  • Handle async function errors effectively ๐Ÿ›
  • Build efficient async applications with TypeScript โœจ

๐ŸŽฏ Introduction

Welcome to the fascinating world of async functions! ๐Ÿ”ฎ While Promise types tell us what weโ€™re working with, async functions show us HOW asynchronous magic happens. Theyโ€™re the elegant wrapper that makes asynchronous code look and feel synchronous!

Think of async functions as magical time machines โฐ - they let you write code that โ€œtravels to the futureโ€ to get values, but your code reads like it happens instantly. No more callback pyramids or complex Promise chains!

By the end of this tutorial, youโ€™ll understand exactly how async functions work under the hood, their return type behavior, and how to use them to build clean, maintainable asynchronous applications. Letโ€™s unlock the secrets! ๐Ÿ”“

๐Ÿ“š Understanding Async Functions

๐Ÿค” What Are Async Functions?

Async functions are like magical translators ๐ŸŒŸ. They take your synchronous-looking code and automatically convert it into Promise-based asynchronous operations. Itโ€™s syntactic sugar that makes async code readable and maintainable!

// ๐Ÿ”ฎ The magic transformation
async function fetchUserData(id: string): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  const userData = await response.json();
  return userData; // โœจ Automatically wrapped in Promise.resolve()
}

// ๐ŸŽญ What it's really doing behind the scenes:
function fetchUserDataExplicit(id: string): Promise<User> {
  return fetch(`/api/users/${id}`)
    .then(response => response.json())
    .then(userData => userData);
}

๐Ÿ’ก Key Characteristics

  • ๐ŸŽ Automatic Promise Wrapping: Return values are automatically wrapped in Promise.resolve()
  • โšก Await Integration: Can use await to pause execution until Promises resolve
  • ๐ŸŽฏ Type Safety: TypeScript provides excellent inference for async return types
  • ๐Ÿ›ก๏ธ Error Handling: Thrown errors automatically become rejected Promises
// ๐ŸŽจ TypeScript's type inference magic
async function getString(): Promise<string> {
  return "hello"; // TypeScript knows this becomes Promise<string>
}

async function getNumber(): Promise<number> {
  if (Math.random() > 0.5) {
    return 42; // Auto-wrapped in Promise.resolve(42)
  }
  throw new Error("Bad luck! ๐ŸŽฒ"); // Auto-wrapped in Promise.reject()
}

๐Ÿ†š Async Functions vs Regular Functions

// ๐Ÿƒโ€โ™‚๏ธ Regular function - synchronous
function calculateSync(a: number, b: number): number {
  return a + b; // Immediate return
}

// ๐Ÿ”ฎ Async function - always returns Promise
async function calculateAsync(a: number, b: number): Promise<number> {
  return a + b; // Wrapped in Promise.resolve()
}

// ๐ŸŒŸ Mixed async/sync operations
async function smartCalculation(a: number, b: number): Promise<number> {
  const intermediate = calculateSync(a, b); // Sync call
  const factor = await getMultiplierFromAPI(); // Async call
  return intermediate * factor; // Final async result
}

๐Ÿ”ง Return Type Behavior

๐Ÿ“ Understanding Promise Return Types

The magic of async functions lies in their automatic Promise wrapping:

// ๐ŸŽฏ Return type examples
async function returnString(): Promise<string> {
  return "hello"; // string becomes Promise<string>
}

async function returnNumber(): Promise<number> {
  return 42; // number becomes Promise<number>
}

async function returnObject(): Promise<{ name: string; age: number }> {
  return { name: "Alice", age: 30 }; // object becomes Promise<object>
}

// ๐Ÿ”ฅ Even if you explicitly return a Promise, TypeScript handles it!
async function returnPromise(): Promise<string> {
  return Promise.resolve("explicit promise"); // Promise<string> stays Promise<string>
}

๐ŸŽจ Complex Return Type Scenarios

// ๐Ÿ—๏ธ E-commerce order processing system
interface OrderItem {
  productId: string;
  quantity: number;
  price: number;
}

interface Order {
  id: string;
  items: OrderItem[];
  customerEmail: string;
  total: number;
  status: 'pending' | 'processing' | 'completed' | 'failed';
}

class OrderProcessor {
  
  // ๐Ÿ›’ Calculate order total (could involve async price lookups)
  async calculateTotal(items: OrderItem[]): Promise<number> {
    let total = 0;
    
    for (const item of items) {
      // ๐Ÿ’ฐ Each price lookup might be async (database/API call)
      const currentPrice = await this.getCurrentPrice(item.productId);
      total += currentPrice * item.quantity;
    }
    
    return total; // number automatically becomes Promise<number>
  }
  
  // ๐Ÿช Get current price from external service
  private async getCurrentPrice(productId: string): Promise<number> {
    // ๐ŸŒ Simulate API call
    const response = await fetch(`/api/products/${productId}/price`);
    const data = await response.json();
    return data.price; // TypeScript infers the return type
  }
  
  // ๐Ÿ“ฆ Process entire order - returns different types based on success/failure
  async processOrder(orderData: Omit<Order, 'id' | 'total' | 'status'>): Promise<Order> {
    try {
      // ๐ŸŽฏ Generate order ID
      const orderId = await this.generateOrderId();
      
      // ๐Ÿ’ฐ Calculate final total
      const total = await this.calculateTotal(orderData.items);
      
      // ๐Ÿ“ง Validate customer
      await this.validateCustomer(orderData.customerEmail);
      
      // ๐Ÿ—๏ธ Create final order
      const order: Order = {
        id: orderId,
        ...orderData,
        total,
        status: 'processing'
      };
      
      // ๐Ÿ’พ Save to database
      await this.saveOrder(order);
      
      return order; // Order becomes Promise<Order>
    } catch (error) {
      console.error('Order processing failed:', error);
      throw new Error(`Failed to process order: ${error.message}`);
    }
  }
  
  // ๐Ÿ”ข Generate unique order ID
  private async generateOrderId(): Promise<string> {
    const timestamp = Date.now();
    const random = Math.random().toString(36).substr(2, 9);
    return `ORDER_${timestamp}_${random}`;
  }
  
  // โœ… Validate customer exists
  private async validateCustomer(email: string): Promise<void> {
    const customer = await this.findCustomerByEmail(email);
    if (!customer) {
      throw new Error(`Customer not found: ${email}`);
    }
    // โšก Notice: void return becomes Promise<void>
  }
  
  // ๐Ÿ” Find customer in database
  private async findCustomerByEmail(email: string): Promise<{ id: string; email: string } | null> {
    // ๐ŸŒ Simulate database lookup
    const response = await fetch(`/api/customers?email=${email}`);
    if (response.status === 404) {
      return null; // null becomes Promise<null>
    }
    return response.json(); // object becomes Promise<object>
  }
  
  // ๐Ÿ’พ Save order to database
  private async saveOrder(order: Order): Promise<void> {
    await fetch('/api/orders', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(order)
    });
    // ๐ŸŽฏ No explicit return - TypeScript infers Promise<void>
  }
}

๐Ÿ’ก Execution Patterns and Behavior

๐ŸŽฎ Sequential vs Parallel Execution

Understanding when operations run sequentially vs in parallel is crucial:

// ๐Ÿ—๏ธ File processing service
class FileProcessor {
  
  // ๐ŸŒ Sequential execution - each await blocks the next
  async processFilesSequentially(filePaths: string[]): Promise<string[]> {
    const results: string[] = [];
    
    for (const filePath of filePaths) {
      console.log(`๐Ÿ“ Processing ${filePath}...`);
      const content = await this.readFile(filePath);     // Wait for this
      const processed = await this.processContent(content); // Then wait for this
      const saved = await this.saveProcessedFile(processed); // Then wait for this
      results.push(saved);
    }
    
    return results; // All files processed one by one
  }
  
  // ๐Ÿš€ Parallel execution - start all operations at once
  async processFilesInParallel(filePaths: string[]): Promise<string[]> {
    // ๐ŸŽฏ Start all read operations immediately
    const readPromises = filePaths.map(filePath => this.readFile(filePath));
    
    // โšก Wait for all reads to complete
    const contents = await Promise.all(readPromises);
    
    // ๐Ÿ”„ Process all contents in parallel
    const processPromises = contents.map(content => this.processContent(content));
    const processed = await Promise.all(processPromises);
    
    // ๐Ÿ’พ Save all processed files in parallel
    const savePromises = processed.map(content => this.saveProcessedFile(content));
    const results = await Promise.all(savePromises);
    
    return results; // Much faster! ๐Ÿƒโ€โ™‚๏ธ
  }
  
  // ๐ŸŽจ Mixed pattern - some sequential, some parallel
  async processFilesWithValidation(filePaths: string[]): Promise<string[]> {
    // ๐Ÿ” First, validate all files exist (parallel)
    console.log('๐Ÿ” Validating files...');
    const validationPromises = filePaths.map(path => this.validateFile(path));
    await Promise.all(validationPromises);
    
    const results: string[] = [];
    
    // ๐Ÿ“ Then process in batches to avoid overwhelming the system
    const batchSize = 3;
    for (let i = 0; i < filePaths.length; i += batchSize) {
      const batch = filePaths.slice(i, i + batchSize);
      console.log(`๐Ÿ“ฆ Processing batch ${Math.floor(i / batchSize) + 1}...`);
      
      // ๐Ÿš€ Process this batch in parallel
      const batchPromises = batch.map(async (filePath) => {
        const content = await this.readFile(filePath);
        const processed = await this.processContent(content);
        return this.saveProcessedFile(processed);
      });
      
      const batchResults = await Promise.all(batchPromises);
      results.push(...batchResults);
    }
    
    return results;
  }
  
  // ๐Ÿ—‚๏ธ Helper methods
  private async readFile(filePath: string): Promise<string> {
    console.log(`๐Ÿ“– Reading ${filePath}`);
    await this.delay(100); // Simulate file I/O
    return `Content of ${filePath}`;
  }
  
  private async processContent(content: string): Promise<string> {
    console.log(`โš™๏ธ Processing content...`);
    await this.delay(200); // Simulate processing time
    return `Processed: ${content}`;
  }
  
  private async saveProcessedFile(content: string): Promise<string> {
    console.log(`๐Ÿ’พ Saving processed file...`);
    await this.delay(150); // Simulate save operation
    return `Saved: ${content}`;
  }
  
  private async validateFile(filePath: string): Promise<void> {
    console.log(`โœ… Validating ${filePath}`);
    await this.delay(50); // Simulate validation
    if (Math.random() < 0.1) { // 10% chance of invalid file
      throw new Error(`Invalid file: ${filePath}`);
    }
  }
  
  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// ๐ŸŽฎ Usage example
async function demonstrateExecutionPatterns(): Promise<void> {
  const processor = new FileProcessor();
  const files = ['file1.txt', 'file2.txt', 'file3.txt', 'file4.txt'];
  
  console.log('๐ŸŒ Sequential processing:');
  const start1 = Date.now();
  await processor.processFilesSequentially(files);
  console.log(`โฑ๏ธ Sequential took: ${Date.now() - start1}ms\n`);
  
  console.log('๐Ÿš€ Parallel processing:');
  const start2 = Date.now();
  await processor.processFilesInParallel(files);
  console.log(`โฑ๏ธ Parallel took: ${Date.now() - start2}ms\n`);
  
  console.log('๐ŸŽจ Mixed pattern:');
  const start3 = Date.now();
  await processor.processFilesWithValidation(files);
  console.log(`โฑ๏ธ Mixed took: ${Date.now() - start3}ms`);
}

๐ŸŽฏ Async Function Context and This Binding

// ๐Ÿ—๏ธ Understanding 'this' in async methods
class DataService {
  private apiUrl = 'https://api.example.com';
  private headers = { 'Authorization': 'Bearer token123' };
  
  // โœ… Async method - 'this' bound correctly
  async fetchData(endpoint: string): Promise<any> {
    const response = await fetch(`${this.apiUrl}/${endpoint}`, {
      headers: this.headers // โœ… 'this' refers to the class instance
    });
    return response.json();
  }
  
  // ๐ŸŽฏ Arrow function async method - always bound
  fetchDataArrow = async (endpoint: string): Promise<any> => {
    const response = await fetch(`${this.apiUrl}/${endpoint}`, {
      headers: this.headers // โœ… 'this' is lexically bound
    });
    return response.json();
  }
  
  // ๐Ÿ”„ Method that returns async function
  createFetcher(): (endpoint: string) => Promise<any> {
    return async (endpoint: string): Promise<any> => {
      const response = await fetch(`${this.apiUrl}/${endpoint}`, {
        headers: this.headers // โœ… 'this' captured from outer scope
      });
      return response.json();
    };
  }
}

๐Ÿš€ Advanced Async Patterns

๐Ÿง™โ€โ™‚๏ธ Async Generators and Iteration

// ๐ŸŒŠ Data streaming with async generators
class DataStream {
  
  // ๐Ÿ”„ Async generator for paginated data
  async* fetchAllPages(baseUrl: string): AsyncGenerator<any[], void, unknown> {
    let page = 1;
    let hasMore = true;
    
    while (hasMore) {
      console.log(`๐Ÿ“„ Fetching page ${page}...`);
      
      const response = await fetch(`${baseUrl}?page=${page}&limit=10`);
      const data = await response.json();
      
      // ๐ŸŽ Yield current page data
      yield data.items;
      
      // ๐Ÿ” Check if there are more pages
      hasMore = data.hasMore;
      page++;
      
      // ๐Ÿ˜ด Rate limiting - wait between requests
      await this.delay(100);
    }
    
    console.log('โœ… All pages fetched!');
  }
  
  // ๐Ÿ—๏ธ Process streamed data
  async processStreamedData(baseUrl: string): Promise<void> {
    // ๐ŸŒŠ Use for-await-of to consume async generator
    for await (const pageData of this.fetchAllPages(baseUrl)) {
      console.log(`๐Ÿ”„ Processing ${pageData.length} items from current page`);
      
      // ๐ŸŽฏ Process each item in the page
      for (const item of pageData) {
        await this.processItem(item);
      }
    }
  }
  
  private async processItem(item: any): Promise<void> {
    console.log(`โš™๏ธ Processing item: ${item.id}`);
    await this.delay(10); // Simulate processing
  }
  
  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

๐Ÿ—๏ธ Async Constructor Patterns

// ๐ŸŽฏ Handling async initialization
class DatabaseConnection {
  private connection: any = null;
  private initialized = false;
  
  private constructor(private connectionString: string) {}
  
  // ๐Ÿญ Static factory method for async creation
  static async create(connectionString: string): Promise<DatabaseConnection> {
    const instance = new DatabaseConnection(connectionString);
    await instance.initialize();
    return instance;
  }
  
  private async initialize(): Promise<void> {
    console.log('๐Ÿ”Œ Connecting to database...');
    
    // ๐ŸŒ Simulate async connection establishment
    this.connection = await this.establishConnection();
    this.initialized = true;
    
    console.log('โœ… Database connected successfully!');
  }
  
  async query(sql: string): Promise<any[]> {
    if (!this.initialized) {
      throw new Error('โŒ Database not initialized. Use DatabaseConnection.create()');
    }
    
    console.log(`๐Ÿ” Executing query: ${sql}`);
    // ๐ŸŽฏ Simulate query execution
    await this.delay(50);
    return [{ id: 1, name: 'Mock Data' }];
  }
  
  private async establishConnection(): Promise<any> {
    await this.delay(200); // Simulate connection time
    return { connected: true, connectionId: Date.now() };
  }
  
  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// ๐ŸŽฎ Usage pattern
async function demonstrateDatabasePattern(): Promise<void> {
  try {
    // โœ… Correct way - use static factory method
    const db = await DatabaseConnection.create('postgresql://localhost:5432/mydb');
    const results = await db.query('SELECT * FROM users');
    console.log('Query results:', results);
  } catch (error) {
    console.error('Database error:', error);
  }
}

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: Floating Promises

// โŒ Dangerous - unhandled promise rejection!
async function dangerousCode(): Promise<void> {
  someAsyncOperation(); // ๐Ÿ’ฅ Floating promise - no await or .catch()
  console.log('This runs immediately!');
}

// โœ… Safe - properly handled
async function safeCode(): Promise<void> {
  try {
    await someAsyncOperation(); // โœ… Properly awaited
    console.log('This runs after async operation!');
  } catch (error) {
    console.error('Handled error:', error);
  }
}

// ๐ŸŽฏ Alternative - fire and forget with explicit error handling
async function fireAndForget(): Promise<void> {
  someAsyncOperation().catch(error => {
    console.error('Background operation failed:', error);
  }); // โœ… Explicitly ignore result but handle errors
  
  console.log('This runs immediately!');
}

async function someAsyncOperation(): Promise<void> {
  await new Promise(resolve => setTimeout(resolve, 100));
  if (Math.random() < 0.3) {
    throw new Error('Random failure!');
  }
}

๐Ÿคฏ Pitfall 2: Serial vs Parallel Confusion

// โŒ Accidentally serial execution
async function slowVersion(urls: string[]): Promise<string[]> {
  const results: string[] = [];
  
  for (const url of urls) {
    const response = await fetch(url); // ๐ŸŒ Waits for each one
    results.push(await response.text());
  }
  
  return results;
}

// โœ… Intentionally parallel execution
async function fastVersion(urls: string[]): Promise<string[]> {
  const promises = urls.map(async (url) => {
    const response = await fetch(url);
    return response.text();
  });
  
  return Promise.all(promises); // ๐Ÿš€ All fetch at once
}

// ๐ŸŽฏ Controlled parallelism
async function controlledVersion(urls: string[], concurrency = 3): Promise<string[]> {
  const results: string[] = [];
  
  for (let i = 0; i < urls.length; i += concurrency) {
    const batch = urls.slice(i, i + concurrency);
    const batchPromises = batch.map(async (url) => {
      const response = await fetch(url);
      return response.text();
    });
    
    const batchResults = await Promise.all(batchPromises);
    results.push(...batchResults);
  }
  
  return results;
}

๐Ÿ”ฅ Pitfall 3: Memory Leaks with Large Arrays

// โŒ Memory intensive - loads everything at once
async function memoryHungry(urls: string[]): Promise<void> {
  const allData = await Promise.all(
    urls.map(url => fetch(url).then(r => r.blob())) // ๐Ÿ’พ All files in memory
  );
  
  // ๐Ÿ”„ Process all data
  for (const data of allData) {
    await processLargeFile(data);
  }
}

// โœ… Memory efficient - streaming approach
async function memoryFriendly(urls: string[]): Promise<void> {
  for (const url of urls) {
    const response = await fetch(url);
    const data = await response.blob();
    await processLargeFile(data); // ๐Ÿ”„ Process one at a time
    // ๐Ÿ—‘๏ธ Data can be garbage collected here
  }
}

async function processLargeFile(data: Blob): Promise<void> {
  console.log(`Processing ${data.size} bytes...`);
  await new Promise(resolve => setTimeout(resolve, 100));
}

๐Ÿ› ๏ธ Best Practices

๐ŸŽฏ Type-Safe Error Handling

// ๐Ÿ›ก๏ธ Type-safe result pattern
type Result<T, E = Error> = {
  success: true;
  data: T;
} | {
  success: false;
  error: E;
};

class ApiService {
  // ๐ŸŽฏ Method that never throws - returns Result instead
  async safeApiCall<T>(url: string): Promise<Result<T>> {
    try {
      const response = await fetch(url);
      
      if (!response.ok) {
        return {
          success: false,
          error: new Error(`HTTP ${response.status}: ${response.statusText}`)
        };
      }
      
      const data = await response.json();
      return {
        success: true,
        data
      };
    } catch (error) {
      return {
        success: false,
        error: error instanceof Error ? error : new Error('Unknown error')
      };
    }
  }
  
  // ๐ŸŽฎ Usage example
  async handleApiCall(): Promise<void> {
    const result = await this.safeApiCall<{ name: string; age: number }>('/api/user/123');
    
    if (result.success) {
      console.log(`User: ${result.data.name}, Age: ${result.data.age}`);
    } else {
      console.error('API call failed:', result.error.message);
    }
  }
}

๐ŸŽจ Performance Monitoring

// ๐Ÿ“Š Performance tracking for async operations
class PerformanceTracker {
  private static measurements = new Map<string, number[]>();
  
  static async measure<T>(
    label: string, 
    operation: () => Promise<T>
  ): Promise<T> {
    const start = performance.now();
    
    try {
      const result = await operation();
      const duration = performance.now() - start;
      
      // ๐Ÿ“ˆ Store measurement
      if (!this.measurements.has(label)) {
        this.measurements.set(label, []);
      }
      this.measurements.get(label)!.push(duration);
      
      console.log(`โฑ๏ธ ${label}: ${duration.toFixed(2)}ms`);
      return result;
    } catch (error) {
      const duration = performance.now() - start;
      console.log(`โŒ ${label} failed after ${duration.toFixed(2)}ms`);
      throw error;
    }
  }
  
  static getStats(label: string): { avg: number; min: number; max: number; count: number } | null {
    const measurements = this.measurements.get(label);
    if (!measurements || measurements.length === 0) return null;
    
    return {
      avg: measurements.reduce((a, b) => a + b, 0) / measurements.length,
      min: Math.min(...measurements),
      max: Math.max(...measurements),
      count: measurements.length
    };
  }
}

// ๐ŸŽฎ Usage example
async function demonstratePerformanceTracking(): Promise<void> {
  // ๐Ÿ“Š Track different operations
  await PerformanceTracker.measure('database-query', async () => {
    await new Promise(resolve => setTimeout(resolve, 100));
    return { rows: 10 };
  });
  
  await PerformanceTracker.measure('api-call', async () => {
    await new Promise(resolve => setTimeout(resolve, 200));
    return { status: 'success' };
  });
  
  // ๐Ÿ“ˆ Get performance statistics
  const dbStats = PerformanceTracker.getStats('database-query');
  const apiStats = PerformanceTracker.getStats('api-call');
  
  console.log('๐Ÿ“Š Performance Summary:');
  if (dbStats) console.log(`Database: avg ${dbStats.avg.toFixed(2)}ms`);
  if (apiStats) console.log(`API: avg ${apiStats.avg.toFixed(2)}ms`);
}

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Build an Async Task Queue

Create a type-safe task queue system that processes async operations with concurrency control:

๐Ÿ“‹ Requirements:

  • โœ… Generic task types with different priorities
  • ๐Ÿท๏ธ Concurrency limiting (max X tasks at once)
  • ๐Ÿ‘ค Task result tracking and error handling
  • ๐Ÿ“… Retry logic for failed tasks
  • ๐ŸŽจ Progress reporting with events

๐Ÿš€ Bonus Points:

  • Add task dependencies (task B runs after task A)
  • Implement task cancellation
  • Create metrics and monitoring

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
// ๐ŸŽฏ Type-safe async task queue system!

interface Task<T> {
  id: string;
  operation: () => Promise<T>;
  priority: 'low' | 'medium' | 'high';
  retries: number;
  maxRetries: number;
  dependencies?: string[];
}

interface TaskResult<T> {
  taskId: string;
  success: boolean;
  data?: T;
  error?: Error;
  attempts: number;
  duration: number;
}

class AsyncTaskQueue {
  private pending = new Map<string, Task<any>>();
  private running = new Map<string, Promise<any>>();
  private completed = new Map<string, TaskResult<any>>();
  private maxConcurrency: number;
  
  constructor(maxConcurrency = 3) {
    this.maxConcurrency = maxConcurrency;
  }
  
  // โž• Add task to queue
  async addTask<T>(task: Omit<Task<T>, 'id' | 'retries'>): Promise<string> {
    const taskId = `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
    
    const fullTask: Task<T> = {
      ...task,
      id: taskId,
      retries: 0
    };
    
    this.pending.set(taskId, fullTask);
    console.log(`๐Ÿ“ Added task ${taskId} (${task.priority} priority)`);
    
    // ๐Ÿš€ Start processing if we have capacity
    this.processQueue();
    
    return taskId;
  }
  
  // ๐Ÿ”„ Process the queue
  private async processQueue(): Promise<void> {
    if (this.running.size >= this.maxConcurrency) {
      return; // At capacity
    }
    
    // ๐ŸŽฏ Find next eligible task
    const nextTask = this.getNextTask();
    if (!nextTask) {
      return; // No eligible tasks
    }
    
    // ๐Ÿƒโ€โ™‚๏ธ Start the task
    this.startTask(nextTask);
    
    // ๐Ÿ”„ Try to start more tasks
    this.processQueue();
  }
  
  // ๐ŸŽฏ Get next task based on priority and dependencies
  private getNextTask(): Task<any> | null {
    const eligibleTasks = Array.from(this.pending.values())
      .filter(task => this.areDependenciesMet(task))
      .sort((a, b) => this.getPriorityWeight(b.priority) - this.getPriorityWeight(a.priority));
    
    return eligibleTasks[0] || null;
  }
  
  // โœ… Check if task dependencies are met
  private areDependenciesMet(task: Task<any>): boolean {
    if (!task.dependencies) return true;
    
    return task.dependencies.every(depId => {
      const result = this.completed.get(depId);
      return result && result.success;
    });
  }
  
  // ๐ŸŽฏ Get priority weight for sorting
  private getPriorityWeight(priority: 'low' | 'medium' | 'high'): number {
    switch (priority) {
      case 'high': return 3;
      case 'medium': return 2;
      case 'low': return 1;
    }
  }
  
  // ๐Ÿš€ Start executing a task
  private async startTask(task: Task<any>): Promise<void> {
    this.pending.delete(task.id);
    console.log(`๐Ÿš€ Starting task ${task.id} (attempt ${task.retries + 1})`);
    
    const startTime = performance.now();
    const taskPromise = this.executeTask(task, startTime);
    this.running.set(task.id, taskPromise);
    
    try {
      await taskPromise;
    } finally {
      this.running.delete(task.id);
      // ๐Ÿ”„ Continue processing queue
      this.processQueue();
    }
  }
  
  // โš™๏ธ Execute individual task with retry logic
  private async executeTask(task: Task<any>, startTime: number): Promise<void> {
    try {
      task.retries++;
      const result = await task.operation();
      const duration = performance.now() - startTime;
      
      // โœ… Success!
      const taskResult: TaskResult<any> = {
        taskId: task.id,
        success: true,
        data: result,
        attempts: task.retries,
        duration
      };
      
      this.completed.set(task.id, taskResult);
      console.log(`โœ… Task ${task.id} completed in ${duration.toFixed(2)}ms`);
      
    } catch (error) {
      const duration = performance.now() - startTime;
      
      if (task.retries < task.maxRetries) {
        // ๐Ÿ”„ Retry the task
        console.log(`โš ๏ธ Task ${task.id} failed, retrying (${task.retries}/${task.maxRetries})`);
        await this.delay(1000 * task.retries); // Exponential backoff
        await this.executeTask(task, performance.now()); // New start time for retry
      } else {
        // โŒ Final failure
        const taskResult: TaskResult<any> = {
          taskId: task.id,
          success: false,
          error: error instanceof Error ? error : new Error('Unknown error'),
          attempts: task.retries,
          duration
        };
        
        this.completed.set(task.id, taskResult);
        console.log(`โŒ Task ${task.id} failed permanently after ${task.retries} attempts`);
      }
    }
  }
  
  // ๐Ÿ“Š Get queue status
  getStatus(): {
    pending: number;
    running: number;
    completed: number;
    successful: number;
    failed: number;
  } {
    const successful = Array.from(this.completed.values()).filter(r => r.success).length;
    const failed = Array.from(this.completed.values()).filter(r => !r.success).length;
    
    return {
      pending: this.pending.size,
      running: this.running.size,
      completed: this.completed.size,
      successful,
      failed
    };
  }
  
  // โณ Wait for specific task
  async waitForTask<T>(taskId: string): Promise<TaskResult<T> | null> {
    // Check if already completed
    if (this.completed.has(taskId)) {
      return this.completed.get(taskId) as TaskResult<T>;
    }
    
    // Check if running
    if (this.running.has(taskId)) {
      await this.running.get(taskId);
      return this.completed.get(taskId) as TaskResult<T> || null;
    }
    
    // Not found
    return null;
  }
  
  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// ๐ŸŽฎ Demo usage
async function demonstrateTaskQueue(): Promise<void> {
  const queue = new AsyncTaskQueue(2); // Max 2 concurrent tasks
  
  // ๐Ÿ“ Add various tasks
  const task1 = await queue.addTask({
    operation: async () => {
      await new Promise(resolve => setTimeout(resolve, 1000));
      return "Task 1 result";
    },
    priority: 'high',
    maxRetries: 2
  });
  
  const task2 = await queue.addTask({
    operation: async () => {
      await new Promise(resolve => setTimeout(resolve, 500));
      if (Math.random() < 0.7) throw new Error("Random failure");
      return "Task 2 result";
    },
    priority: 'medium',
    maxRetries: 3
  });
  
  const task3 = await queue.addTask({
    operation: async () => {
      await new Promise(resolve => setTimeout(resolve, 200));
      return "Task 3 result";
    },
    priority: 'low',
    maxRetries: 1,
    dependencies: [task1] // Wait for task1 to complete
  });
  
  // ๐Ÿ“Š Monitor progress
  const monitor = setInterval(() => {
    const status = queue.getStatus();
    console.log(`๐Ÿ“Š Status - Pending: ${status.pending}, Running: ${status.running}, Completed: ${status.completed} (${status.successful} successful, ${status.failed} failed)`);
  }, 500);
  
  // โณ Wait for specific task
  const result1 = await queue.waitForTask(task1);
  console.log('Task 1 result:', result1);
  
  // โณ Wait a bit for all tasks to complete
  setTimeout(() => {
    clearInterval(monitor);
    console.log('Final status:', queue.getStatus());
  }, 5000);
}

๐ŸŽ“ Key Takeaways

Youโ€™ve mastered async functions! Hereโ€™s what you can now do:

  • โœ… Understand async function mechanics and automatic Promise wrapping ๐Ÿ’ช
  • โœ… Design efficient execution patterns for performance ๐Ÿ›ก๏ธ
  • โœ… Handle complex async scenarios with confidence ๐ŸŽฏ
  • โœ… Avoid common async pitfalls like a pro ๐Ÿ›
  • โœ… Build scalable async applications with TypeScript! ๐Ÿš€

Remember: Async functions are syntactic sugar that makes complex asynchronous operations look simple - but understanding what happens under the hood makes you a better developer! ๐Ÿค

๐Ÿค Next Steps

Congratulations! ๐ŸŽ‰ Youโ€™ve mastered async functions and their behavior!

Hereโ€™s what to do next:

  1. ๐Ÿ’ป Practice with the task queue exercise above
  2. ๐Ÿ—๏ธ Build a project using different async patterns
  3. ๐Ÿ“š Move on to our next tutorial: โ€œAsync Generators: Yielding Promisesโ€
  4. ๐ŸŒŸ Share your async function knowledge with others!

Remember: Every async expert was once confused by Promises. Keep coding, keep learning, and embrace the asynchronous future! ๐Ÿš€


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