+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 110 of 355

๐Ÿ Promise.race: First to Complete in TypeScript

Master Promise.race for competitive async operations with practical examples, timeouts, and performance patterns ๐Ÿš€

๐Ÿš€Intermediate
18 min read

Prerequisites

  • Understanding of Promise<T> fundamentals ๐Ÿ“
  • Basic async/await knowledge โšก
  • TypeScript generics experience ๐Ÿ’ป

What you'll learn

  • Master Promise.race mechanics and use cases ๐ŸŽฏ
  • Implement timeout patterns and competitive requests ๐Ÿ—๏ธ
  • Handle race condition scenarios safely ๐Ÿ›
  • Build resilient async applications with fallbacks โœจ

๐ŸŽฏ Introduction

Welcome to the thrilling world of Promise racing! ๐Ÿ Imagine having multiple racers competing to bring you data - API endpoints, database queries, or file operations all racing to the finish line. Promise.race() lets you work with whichever promise crosses the finish line first!

Whether youโ€™re implementing timeout patterns โฐ, building fallback systems ๐Ÿ›ก๏ธ, or optimizing for the fastest response time ๐Ÿš€, Promise.race is your go-to tool for competitive async operations.

By the end of this tutorial, youโ€™ll be a Promise racing champion, able to orchestrate complex async scenarios where speed and resilience matter most. Letโ€™s start your engines! ๐ŸŽ๏ธ

๐Ÿ“š Understanding Promise.race()

๐Ÿค” What is Promise.race()?

Think of Promise.race() as a sprint competition ๐Ÿƒโ€โ™‚๏ธ๐Ÿƒโ€โ™€๏ธ. You line up several runners (promises), fire the starting gun, and whoever finishes first wins the entire race - regardless of what happens to the other runners!

// ๐Ÿ Basic Promise.race example
const fastPromise = new Promise<string>((resolve) => {
  setTimeout(() => resolve("I'm fast! ๐Ÿš€"), 100);
});

const slowPromise = new Promise<string>((resolve) => {
  setTimeout(() => resolve("I'm slow... ๐ŸŒ"), 1000);
});

// ๐Ÿ† Race them - fastest wins!
const winner = await Promise.race([fastPromise, slowPromise]);
console.log(winner); // "I'm fast! ๐Ÿš€" (after ~100ms)

๐Ÿ’ก Key Characteristics

  • ๐Ÿฅ‡ First Wins All: The first settled promise (resolved OR rejected) determines the outcome
  • ๐ŸŽฏ Type Safety: TypeScript preserves union types across all competing promises
  • โšก Immediate: Returns as soon as ANY promise settles
  • ๐Ÿƒโ€โ™‚๏ธ No Waiting: Doesnโ€™t wait for slower promises to complete
// ๐ŸŽจ TypeScript's type inference with Promise.race
const raceTypes = Promise.race([
  Promise.resolve("string result"), // Promise<string>
  Promise.resolve(42),              // Promise<number>
  Promise.resolve(true)             // Promise<boolean>
]); // Type: Promise<string | number | boolean>

๐Ÿ”ง Basic Syntax and Usage

๐Ÿ“ Simple Racing Examples

Letโ€™s explore the fundamentals:

// ๐ŸŽฎ Gaming scenario: Multiple save locations
interface SaveData {
  playerId: string;
  level: number;
  score: number;
  timestamp: Date;
}

class GameSaveService {
  
  // ๐Ÿ’พ Save to local storage (fast)
  private saveToLocal(data: SaveData): Promise<string> {
    return new Promise((resolve) => {
      setTimeout(() => {
        localStorage.setItem('gameData', JSON.stringify(data));
        resolve("โœ… Saved locally!");
      }, 50); // Very fast
    });
  }
  
  // โ˜๏ธ Save to cloud (slower but persistent)
  private saveToCloud(data: SaveData): Promise<string> {
    return new Promise((resolve) => {
      setTimeout(() => {
        // Simulate cloud API call
        resolve("โ˜๏ธ Saved to cloud!");
      }, 300); // Slower
    });
  }
  
  // ๐ŸŽฏ Save wherever is fastest!
  async quickSave(data: SaveData): Promise<string> {
    console.log("๐Ÿ Starting save race...");
    
    const localSave = this.saveToLocal(data);
    const cloudSave = this.saveToCloud(data);
    
    // ๐Ÿ† Race them - first one wins!
    const result = await Promise.race([localSave, cloudSave]);
    console.log(`๐ŸŽ‰ Winner: ${result}`);
    
    return result; // TypeScript knows this is string
  }
}

// ๐ŸŽฎ Using the game save service
async function demonstrateGameSave() {
  const saveService = new GameSaveService();
  
  const gameData: SaveData = {
    playerId: "player123",
    level: 42,
    score: 98765,
    timestamp: new Date()
  };
  
  const winner = await saveService.quickSave(gameData);
  console.log(`๐Ÿ† Save completed: ${winner}`);
}

๐ŸŽฏ Timeout Patterns

One of the most common uses of Promise.race is implementing timeouts:

// โฐ Timeout utility function
function withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {
  const timeoutPromise = new Promise<never>((_, reject) => {
    setTimeout(() => {
      reject(new Error(`โฐ Operation timed out after ${timeoutMs}ms`));
    }, timeoutMs);
  });
  
  return Promise.race([promise, timeoutPromise]);
}

// ๐ŸŒ API service with timeout protection
class APIService {
  
  // ๐Ÿ“ก Fetch user data with timeout
  async fetchUserWithTimeout(userId: string): Promise<{ name: string; email: string }> {
    const userPromise = fetch(`/api/users/${userId}`)
      .then(response => {
        if (!response.ok) {
          throw new Error(`HTTP ${response.status}: ${response.statusText}`);
        }
        return response.json();
      });
    
    // ๐Ÿ Race against timeout
    return withTimeout(userPromise, 5000); // 5 second timeout
  }
  
  // ๐Ÿ”„ Multiple endpoints race
  async getFastestUserData(userId: string): Promise<any> {
    const endpoints = [
      `/api/v1/users/${userId}`,    // Old API
      `/api/v2/users/${userId}`,    // New API
      `/cdn/users/${userId}.json`   // CDN cache
    ];
    
    const promises = endpoints.map(endpoint => 
      fetch(endpoint).then(res => res.json())
    );
    
    // ๐Ÿ† First successful response wins
    return Promise.race(promises);
  }
}

๐Ÿ’ก Practical Examples

๐Ÿ›’ Example 1: Smart Shopping Price Checker

Letโ€™s build a price comparison service that gets the fastest price quote:

// ๐Ÿช Store and pricing interfaces
interface Product {
  id: string;
  name: string;
  emoji: string;
}

interface PriceQuote {
  store: string;
  price: number;
  availability: boolean;
  shippingTime: string;
  storeEmoji: string;
}

interface PriceComparisonResult {
  fastestQuote: PriceQuote;
  allQuotes?: PriceQuote[];
  responseTime: number;
}

class PriceComparison {
  
  // ๐Ÿ›๏ธ Get quote from Amazon (slow but reliable)
  private getAmazonPrice(product: Product): Promise<PriceQuote> {
    return new Promise((resolve) => {
      const delay = 800 + Math.random() * 400; // 800-1200ms
      setTimeout(() => {
        resolve({
          store: "Amazon",
          price: 29.99 + Math.random() * 10,
          availability: true,
          shippingTime: "2 days",
          storeEmoji: "๐Ÿ“ฆ"
        });
      }, delay);
    });
  }
  
  // ๐ŸŽฏ Get quote from Best Buy (medium speed)
  private getBestBuyPrice(product: Product): Promise<PriceQuote> {
    return new Promise((resolve) => {
      const delay = 400 + Math.random() * 300; // 400-700ms
      setTimeout(() => {
        resolve({
          store: "Best Buy",
          price: 32.99 + Math.random() * 8,
          availability: Math.random() > 0.2, // 80% chance available
          shippingTime: "3-5 days",
          storeEmoji: "๐Ÿช"
        });
      }, delay);
    });
  }
  
  // โšก Get quote from local store (fast)
  private getLocalPrice(product: Product): Promise<PriceQuote> {
    return new Promise((resolve) => {
      const delay = 150 + Math.random() * 200; // 150-350ms
      setTimeout(() => {
        resolve({
          store: "Local Store",
          price: 35.99 + Math.random() * 5,
          availability: Math.random() > 0.3, // 70% chance available
          shippingTime: "Same day",
          storeEmoji: "๐Ÿฌ"
        });
      }, delay);
    });
  }
  
  // ๐Ÿ Get fastest price quote
  async getFastestPrice(product: Product): Promise<PriceComparisonResult> {
    console.log(`๐Ÿ” Searching for best price on ${product.emoji} ${product.name}...`);
    const startTime = Date.now();
    
    const amazonQuote = this.getAmazonPrice(product);
    const bestBuyQuote = this.getBestBuyPrice(product);
    const localQuote = this.getLocalPrice(product);
    
    try {
      // ๐Ÿ† Race for the fastest quote
      const fastestQuote = await Promise.race([amazonQuote, bestBuyQuote, localQuote]);
      const responseTime = Date.now() - startTime;
      
      console.log(`โšก Fastest response from ${fastestQuote.storeEmoji} ${fastestQuote.store} in ${responseTime}ms!`);
      
      return {
        fastestQuote,
        responseTime
      };
      
    } catch (error) {
      throw new Error(`๐Ÿ’ฅ All price checks failed: ${error.message}`);
    }
  }
  
  // ๐ŸŽฏ Get fastest available product
  async getFastestAvailablePrice(product: Product): Promise<PriceComparisonResult> {
    console.log(`๐ŸŽฏ Finding fastest available price for ${product.emoji} ${product.name}...`);
    const startTime = Date.now();
    
    // ๐Ÿ”„ Keep racing until we find an available item
    const stores = [
      () => this.getAmazonPrice(product),
      () => this.getBestBuyPrice(product),
      () => this.getLocalPrice(product)
    ];
    
    for (let attempt = 0; attempt < 3; attempt++) {
      try {
        const promises = stores.map(storeFunc => storeFunc());
        const quote = await Promise.race(promises);
        
        if (quote.availability) {
          const responseTime = Date.now() - startTime;
          console.log(`โœ… Found available item at ${quote.storeEmoji} ${quote.store}!`);
          return { fastestQuote: quote, responseTime };
        } else {
          console.log(`โŒ ${quote.store} is out of stock, trying others...`);
        }
      } catch (error) {
        console.log(`๐Ÿ”„ Attempt ${attempt + 1} failed, retrying...`);
      }
    }
    
    throw new Error("๐Ÿ˜ž No stores have this item available");
  }
}

// ๐ŸŽฎ Demo the price comparison
async function demonstratePriceComparison() {
  const priceChecker = new PriceComparison();
  
  const product: Product = {
    id: "ts-book-001",
    name: "TypeScript Handbook",
    emoji: "๐Ÿ“˜"
  };
  
  try {
    // ๐Ÿ Race for fastest price
    console.log("๐Ÿš€ Starting price race...\n");
    const result = await priceChecker.getFastestPrice(product);
    
    console.log(`\n๐Ÿ† Winner: ${result.fastestQuote.storeEmoji} ${result.fastestQuote.store}`);
    console.log(`๐Ÿ’ฐ Price: $${result.fastestQuote.price.toFixed(2)}`);
    console.log(`๐Ÿ“ฆ Shipping: ${result.fastestQuote.shippingTime}`);
    console.log(`โšก Response time: ${result.responseTime}ms\n`);
    
    // ๐ŸŽฏ Find fastest available
    const availableResult = await priceChecker.getFastestAvailablePrice(product);
    console.log(`\nโœ… Fastest available option: ${availableResult.fastestQuote.storeEmoji} ${availableResult.fastestQuote.store}`);
    
  } catch (error) {
    console.error("๐Ÿ’ฅ Price comparison failed:", error.message);
  }
}

๐ŸŒŸ Example 2: Multi-Server Health Check

Letโ€™s create a system that checks which server responds fastest:

// ๐Ÿ–ฅ๏ธ Server and health check interfaces
interface ServerInfo {
  name: string;
  url: string;
  region: string;
  emoji: string;
}

interface HealthCheckResult {
  server: ServerInfo;
  status: "healthy" | "degraded" | "down";
  responseTime: number;
  timestamp: Date;
}

interface FastestServerResult {
  fastest: HealthCheckResult;
  allResults?: HealthCheckResult[];
  totalCheckTime: number;
}

class ServerHealthChecker {
  private servers: ServerInfo[] = [
    { name: "US East", url: "https://api-east.example.com", region: "us-east-1", emoji: "๐Ÿ‡บ๐Ÿ‡ธ" },
    { name: "EU West", url: "https://api-eu.example.com", region: "eu-west-1", emoji: "๐Ÿ‡ช๐Ÿ‡บ" },
    { name: "Asia Pacific", url: "https://api-asia.example.com", region: "ap-southeast-1", emoji: "๐ŸŒ" },
    { name: "Australia", url: "https://api-au.example.com", region: "ap-southeast-2", emoji: "๐Ÿ‡ฆ๐Ÿ‡บ" }
  ];
  
  // ๐Ÿฅ Check single server health
  private async checkServerHealth(server: ServerInfo): Promise<HealthCheckResult> {
    const startTime = Date.now();
    
    try {
      // ๐ŸŒ Simulate health check (replace with real fetch in production)
      await new Promise((resolve, reject) => {
        const responseTime = 50 + Math.random() * 500; // 50-550ms
        const isHealthy = Math.random() > 0.1; // 90% success rate
        
        setTimeout(() => {
          if (isHealthy) {
            resolve(undefined);
          } else {
            reject(new Error("Server timeout"));
          }
        }, responseTime);
      });
      
      const responseTime = Date.now() - startTime;
      
      return {
        server,
        status: responseTime < 200 ? "healthy" : "degraded",
        responseTime,
        timestamp: new Date()
      };
      
    } catch (error) {
      return {
        server,
        status: "down",
        responseTime: Date.now() - startTime,
        timestamp: new Date()
      };
    }
  }
  
  // ๐Ÿ Find fastest healthy server
  async getFastestHealthyServer(): Promise<FastestServerResult> {
    console.log("๐Ÿ” Checking server health across all regions...");
    const startTime = Date.now();
    
    // ๐Ÿš€ Race all servers simultaneously
    const healthChecks = this.servers.map(server => this.checkServerHealth(server));
    
    try {
      // ๐Ÿ† First healthy server wins
      const fastest = await Promise.race(healthChecks);
      const totalCheckTime = Date.now() - startTime;
      
      if (fastest.status === "down") {
        throw new Error("Fastest server is down, checking others...");
      }
      
      console.log(`โšก Fastest server: ${fastest.server.emoji} ${fastest.server.name} (${fastest.responseTime}ms)`);
      
      return {
        fastest,
        totalCheckTime
      };
      
    } catch (error) {
      console.log("๐Ÿ”„ First server failed, waiting for others...");
      
      // ๐Ÿ›ก๏ธ Fallback: wait for all results and pick best healthy one
      const allResults = await Promise.allSettled(healthChecks);
      const healthyServers = allResults
        .filter((result): result is PromiseFulfilledResult<HealthCheckResult> => 
          result.status === 'fulfilled' && result.value.status !== 'down'
        )
        .map(result => result.value)
        .sort((a, b) => a.responseTime - b.responseTime);
      
      if (healthyServers.length === 0) {
        throw new Error("๐Ÿ˜ž All servers are down!");
      }
      
      const fastest = healthyServers[0];
      const totalCheckTime = Date.now() - startTime;
      
      console.log(`๐Ÿ›ก๏ธ Fallback server: ${fastest.server.emoji} ${fastest.server.name}`);
      
      return {
        fastest,
        allResults: allResults.map(r => r.status === 'fulfilled' ? r.value : null).filter(Boolean) as HealthCheckResult[],
        totalCheckTime
      };
    }
  }
  
  // ๐Ÿ“Š Get comprehensive health report
  async getHealthReport(): Promise<{ fastest: HealthCheckResult; all: HealthCheckResult[] }> {
    console.log("๐Ÿ“Š Generating comprehensive health report...");
    
    const healthChecks = this.servers.map(server => this.checkServerHealth(server));
    
    // ๐Ÿ Race for fastest + wait for all
    const [fastest, ...allResults] = await Promise.all([
      Promise.race(healthChecks),
      ...healthChecks
    ]);
    
    return { fastest, all: allResults };
  }
}

// ๐ŸŽฎ Demo the server health checker
async function demonstrateServerHealthCheck() {
  const healthChecker = new ServerHealthChecker();
  
  try {
    // ๐Ÿ Find fastest server
    console.log("๐Ÿš€ Racing servers for fastest response...\n");
    const result = await healthChecker.getFastestHealthyServer();
    
    console.log(`\n๐Ÿ† Fastest healthy server:`);
    console.log(`  ${result.fastest.server.emoji} ${result.fastest.server.name}`);
    console.log(`  ๐Ÿ“ Region: ${result.fastest.server.region}`);
    console.log(`  โšก Response: ${result.fastest.responseTime}ms`);
    console.log(`  ๐Ÿ’š Status: ${result.fastest.status}`);
    console.log(`  โฑ๏ธ Total check time: ${result.totalCheckTime}ms\n`);
    
    // ๐Ÿ“Š Get full report
    console.log("๐Ÿ“Š Getting full health report...");
    const report = await healthChecker.getHealthReport();
    
    console.log(`\n๐Ÿ“ˆ All server results:`);
    report.all.forEach(result => {
      const statusEmoji = result.status === 'healthy' ? '๐Ÿ’š' : result.status === 'degraded' ? '๐Ÿ’›' : 'โค๏ธ';
      console.log(`  ${result.server.emoji} ${result.server.name}: ${statusEmoji} ${result.status} (${result.responseTime}ms)`);
    });
    
  } catch (error) {
    console.error("๐Ÿ’ฅ Health check failed:", error.message);
  }
}

๐Ÿš€ Advanced Concepts

๐Ÿง™โ€โ™‚๏ธ Competitive Loading with Fallbacks

Handle complex scenarios where you want the fastest result but need fallbacks:

// ๐ŸŽฏ Advanced race with intelligent fallbacks
class SmartDataLoader<T> {
  
  // ๐Ÿ Race with fallback chain
  async loadWithFallbacks<T>(
    primary: () => Promise<T>,
    fallbacks: (() => Promise<T>)[],
    timeoutMs: number = 5000
  ): Promise<{ data: T; source: string; attemptCount: number }> {
    
    // ๐ŸŽฏ Try primary first
    try {
      const primaryPromise = primary();
      const timeoutPromise = new Promise<never>((_, reject) => {
        setTimeout(() => reject(new Error('Timeout')), timeoutMs);
      });
      
      const data = await Promise.race([primaryPromise, timeoutPromise]);
      return { data, source: 'primary', attemptCount: 1 };
      
    } catch (primaryError) {
      console.log("๐Ÿ”„ Primary failed, trying fallbacks...");
      
      // ๐Ÿ›ก๏ธ Race all fallbacks
      if (fallbacks.length === 0) {
        throw new Error("No fallbacks available");
      }
      
      for (let i = 0; i < fallbacks.length; i++) {
        try {
          const fallbackPromises = fallbacks.slice(i).map(fallback => fallback());
          const timeoutPromise = new Promise<never>((_, reject) => {
            setTimeout(() => reject(new Error('Fallback timeout')), timeoutMs);
          });
          
          const data = await Promise.race([...fallbackPromises, timeoutPromise]);
          return { data, source: `fallback-${i + 1}`, attemptCount: i + 2 };
          
        } catch (fallbackError) {
          console.log(`๐Ÿ”„ Fallback ${i + 1} failed...`);
          continue;
        }
      }
      
      throw new Error("All sources failed");
    }
  }
  
  // ๐ŸŽฎ Smart cache-first loading
  async smartLoad(
    cacheLoader: () => Promise<T>,
    networkLoader: () => Promise<T>,
    maxCacheAge: number = 60000 // 1 minute
  ): Promise<{ data: T; source: 'cache' | 'network'; fresh: boolean }> {
    
    const cachePromise = cacheLoader().then(data => ({ data, source: 'cache' as const }));
    const networkPromise = networkLoader().then(data => ({ data, source: 'network' as const }));
    
    try {
      // ๐Ÿ Race cache vs network (cache usually wins)
      const result = await Promise.race([cachePromise, networkPromise]);
      
      if (result.source === 'cache') {
        // ๐Ÿ” Check if cache is fresh enough
        const cacheTimestamp = Date.now(); // In real app, get from cache metadata
        const isFresh = cacheTimestamp > Date.now() - maxCacheAge;
        
        if (isFresh) {
          console.log("โšก Using fresh cache data");
          return { ...result, fresh: true };
        } else {
          console.log("๐Ÿ”„ Cache stale, waiting for network...");
          const networkResult = await networkPromise;
          return { ...networkResult, fresh: true };
        }
      }
      
      console.log("๐ŸŒ Using network data");
      return { ...result, fresh: true };
      
    } catch (error) {
      throw new Error(`Smart load failed: ${error.message}`);
    }
  }
}

// ๐ŸŽฎ Using smart data loader
async function demonstrateSmartLoader() {
  const loader = new SmartDataLoader<string>();
  
  // ๐Ÿ“Š Example: Loading user profile with fallbacks
  const primaryLoader = () => 
    new Promise<string>((resolve, reject) => {
      setTimeout(() => {
        Math.random() > 0.7 ? resolve("Primary data ๐ŸŽฏ") : reject(new Error("Primary failed"));
      }, 200);
    });
  
  const fallbackLoaders = [
    () => new Promise<string>((resolve) => 
      setTimeout(() => resolve("Fallback 1 data ๐Ÿ›ก๏ธ"), 300)
    ),
    () => new Promise<string>((resolve) => 
      setTimeout(() => resolve("Fallback 2 data ๐Ÿ†˜"), 400)
    )
  ];
  
  try {
    const result = await loader.loadWithFallbacks(primaryLoader, fallbackLoaders, 1000);
    console.log(`โœ… Loaded from ${result.source} in ${result.attemptCount} attempts: ${result.data}`);
  } catch (error) {
    console.error("๐Ÿ’ฅ All loading attempts failed:", error.message);
  }
}

๐Ÿ—๏ธ Race with Progress Tracking

Track which promises are still racing:

// ๐Ÿ“Š Race with detailed progress tracking
class ProgressiveRace<T> {
  
  async raceWithProgress<T>(
    promises: Promise<T>[],
    onProgress?: (completed: number, total: number, result?: T) => void
  ): Promise<T> {
    
    return new Promise((resolve, reject) => {
      let completed = 0;
      let resolved = false;
      const total = promises.length;
      
      promises.forEach((promise, index) => {
        promise
          .then(result => {
            completed++;
            onProgress?.(completed, total, result);
            
            if (!resolved) {
              resolved = true;
              console.log(`๐Ÿ† Promise ${index + 1} won the race!`);
              resolve(result);
            }
          })
          .catch(error => {
            completed++;
            onProgress?.(completed, total);
            
            if (completed === total && !resolved) {
              reject(new Error("All promises failed"));
            }
          });
      });
    });
  }
}

// ๐ŸŽฎ Demo progressive race
async function demonstrateProgressiveRace() {
  const progressRace = new ProgressiveRace();
  
  const promises = [
    new Promise<string>(resolve => setTimeout(() => resolve("Result A ๐Ÿ…ฐ๏ธ"), 800)),
    new Promise<string>(resolve => setTimeout(() => resolve("Result B ๐Ÿ…ฑ๏ธ"), 300)),
    new Promise<string>(resolve => setTimeout(() => resolve("Result C ๐Ÿ†˜"), 1200))
  ];
  
  console.log("๐Ÿ Starting progressive race...");
  
  try {
    const winner = await progressRace.raceWithProgress(
      promises,
      (completed, total, result) => {
        if (result) {
          console.log(`๐ŸŽฏ First result: ${result}`);
        }
        console.log(`๐Ÿ“Š Progress: ${completed}/${total} promises completed`);
      }
    );
    
    console.log(`๐ŸŽ‰ Final winner: ${winner}`);
  } catch (error) {
    console.error("๐Ÿ’ฅ Race failed:", error.message);
  }
}

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: Ignoring Rejected Promises

// โŒ Dangerous - race can reject immediately!
async function badRace() {
  const promises = [
    Promise.reject(new Error("I fail fast! ๐Ÿ’ฅ")),     // Rejects immediately
    Promise.resolve("I'm slow but successful ๐ŸŒ")     // Resolves later
  ];
  
  // ๐Ÿ’ฅ This will throw immediately!
  return Promise.race(promises);
}

// โœ… Safe - handle rejections properly
async function safeRace() {
  const promises = [
    Promise.reject(new Error("I fail fast! ๐Ÿ’ฅ")).catch(() => null),
    Promise.resolve("I'm slow but successful ๐ŸŒ")
  ];
  
  const result = await Promise.race(promises);
  return result || "No successful result";
}

// โœ… Even better - filter failed promises
async function smartRace<T>(promises: Promise<T>[]): Promise<T> {
  const wrappedPromises = promises.map(async (promise, index) => {
    try {
      const result = await promise;
      return { success: true, result, index };
    } catch (error) {
      return { success: false, error, index };
    }
  });
  
  const firstResult = await Promise.race(wrappedPromises);
  
  if (firstResult.success) {
    return firstResult.result;
  } else {
    throw new Error(`Promise ${firstResult.index} failed: ${firstResult.error.message}`);
  }
}

๐Ÿคฏ Pitfall 2: Memory Leaks with Unfinished Promises

// โŒ Potential memory leak - other promises keep running
async function leakyRace() {
  const promises = [
    fetch('/fast-api'),           // Finishes first
    fetch('/slow-api'),           // Still running in background!
    fetch('/very-slow-api')       // Still running in background!
  ];
  
  return Promise.race(promises); // Other fetches continue consuming resources
}

// โœ… Clean up with AbortController
async function cleanRace() {
  const controller = new AbortController();
  
  const promises = [
    fetch('/fast-api', { signal: controller.signal }),
    fetch('/slow-api', { signal: controller.signal }),
    fetch('/very-slow-api', { signal: controller.signal })
  ];
  
  try {
    const result = await Promise.race(promises);
    controller.abort(); // ๐Ÿงน Cancel other requests
    return result;
  } catch (error) {
    controller.abort(); // ๐Ÿงน Clean up on error too
    throw error;
  }
}

๐ŸŽฏ Pitfall 3: Wrong Type Expectations

// โŒ Wrong - assuming all promises have same type
function badTyping() {
  const promises = [
    Promise.resolve("string"),
    Promise.resolve(42),
    Promise.resolve(true)
  ];
  
  // ๐Ÿšซ TypeScript error - result could be string | number | boolean
  return Promise.race(promises).then((result: string) => result.toUpperCase());
}

// โœ… Correct - handle union types properly
function goodTyping() {
  const promises = [
    Promise.resolve("string"),
    Promise.resolve(42),
    Promise.resolve(true)
  ];
  
  return Promise.race(promises).then(result => {
    if (typeof result === 'string') {
      return result.toUpperCase(); // โœ… TypeScript knows this is safe
    }
    return String(result).toUpperCase(); // ๐ŸŽฏ Convert others to string
  });
}

๐Ÿ› ๏ธ Best Practices

  1. ๐Ÿ›ก๏ธ Always Handle Rejections: First settled promise (even failures) wins the race
  2. ๐Ÿงน Clean Up Resources: Use AbortController or cleanup logic for unfinished promises
  3. ๐ŸŽฏ Type Union Handling: Expect union types when racing different promise types
  4. โฐ Implement Timeouts: Use race for timeout patterns with actual operations
  5. ๐Ÿ”„ Plan for Failures: Have fallback strategies when fastest promise fails
  6. ๐Ÿ“Š Consider All Results: Sometimes you need the fastest + all results for comparison

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Build a Multi-Source Data Aggregator

Create a system that races multiple data sources but falls back intelligently:

๐Ÿ“‹ Requirements:

  • ๐ŸŒ Race multiple API endpoints for fastest response
  • โฐ Implement timeout protection (2 seconds)
  • ๐Ÿ›ก๏ธ Fall back to cached data if all APIs fail
  • ๐Ÿ“Š Track response times and success rates
  • ๐Ÿ”„ Retry failed sources with exponential backoff
  • ๐Ÿงน Clean up ongoing requests when one wins

๐Ÿš€ Bonus Points:

  • Add health monitoring for each source
  • Implement adaptive timeout based on historical performance
  • Create a load balancer that prefers faster sources
  • Add real-time performance metrics dashboard

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
// ๐Ÿ—๏ธ Multi-source data aggregator with intelligent racing
interface DataSource {
  name: string;
  url: string;
  priority: number;
  emoji: string;
}

interface SourceResult<T> {
  data: T;
  source: DataSource;
  responseTime: number;
  fromCache: boolean;
}

interface SourceMetrics {
  totalRequests: number;
  successCount: number;
  avgResponseTime: number;
  lastError?: string;
}

class MultiSourceAggregator<T> {
  private metrics = new Map<string, SourceMetrics>();
  private cache = new Map<string, { data: T; timestamp: number }>();
  private activeControllers = new Set<AbortController>();
  
  constructor(
    private sources: DataSource[],
    private cacheMaxAge: number = 60000 // 1 minute
  ) {
    // ๐Ÿ“Š Initialize metrics
    sources.forEach(source => {
      this.metrics.set(source.name, {
        totalRequests: 0,
        successCount: 0,
        avgResponseTime: 0
      });
    });
  }
  
  // ๐Ÿ Race all sources with intelligent fallbacks
  async fetchData(endpoint: string, timeoutMs: number = 2000): Promise<SourceResult<T>> {
    console.log(`๐Ÿ Racing ${this.sources.length} sources for: ${endpoint}`);
    
    // ๐Ÿงน Clean up any previous requests
    this.cleanupActiveRequests();
    
    const racePromises = this.sources.map(source => 
      this.fetchFromSource(source, endpoint, timeoutMs)
    );
    
    try {
      // ๐Ÿ† Race all sources
      const result = await Promise.race(racePromises);
      console.log(`โšก Winner: ${result.source.emoji} ${result.source.name} (${result.responseTime}ms)`);
      
      // ๐Ÿงน Cancel other requests
      this.cleanupActiveRequests();
      
      // ๐Ÿ’พ Cache the result
      this.cacheResult(endpoint, result.data);
      
      return result;
      
    } catch (error) {
      console.log("๐Ÿ”„ All sources failed, checking cache...");
      
      // ๐Ÿ›ก๏ธ Fallback to cache
      const cachedData = this.getCachedData(endpoint);
      if (cachedData) {
        console.log("๐Ÿ’พ Using cached data as fallback");
        return {
          data: cachedData,
          source: { name: "Cache", url: "local", priority: 0, emoji: "๐Ÿ’พ" },
          responseTime: 0,
          fromCache: true
        };
      }
      
      throw new Error(`All sources failed and no cache available: ${error.message}`);
    }
  }
  
  // ๐ŸŒ Fetch from a single source
  private async fetchFromSource(
    source: DataSource, 
    endpoint: string, 
    timeoutMs: number
  ): Promise<SourceResult<T>> {
    const controller = new AbortController();
    this.activeControllers.add(controller);
    
    const startTime = Date.now();
    const metrics = this.metrics.get(source.name)!;
    metrics.totalRequests++;
    
    try {
      // ๐ŸŒ Simulate API call (replace with real fetch)
      const data = await this.simulateAPICall(source, endpoint, controller.signal, timeoutMs);
      const responseTime = Date.now() - startTime;
      
      // ๐Ÿ“Š Update metrics
      metrics.successCount++;
      metrics.avgResponseTime = (metrics.avgResponseTime + responseTime) / 2;
      
      this.activeControllers.delete(controller);
      
      return {
        data,
        source,
        responseTime,
        fromCache: false
      };
      
    } catch (error) {
      const responseTime = Date.now() - startTime;
      metrics.lastError = error.message;
      
      this.activeControllers.delete(controller);
      throw new Error(`${source.emoji} ${source.name} failed (${responseTime}ms): ${error.message}`);
    }
  }
  
  // ๐ŸŽฒ Simulate API call (replace with real implementation)
  private simulateAPICall(
    source: DataSource, 
    endpoint: string, 
    signal: AbortSignal,
    timeoutMs: number
  ): Promise<T> {
    return new Promise((resolve, reject) => {
      // ๐ŸŽฏ Simulate different response times based on priority
      const baseDelay = (6 - source.priority) * 200; // Higher priority = lower delay
      const randomDelay = Math.random() * 500;
      const totalDelay = baseDelay + randomDelay;
      
      const timeoutId = setTimeout(() => {
        if (signal.aborted) {
          reject(new Error("Request aborted"));
          return;
        }
        
        // ๐ŸŽฒ 90% success rate
        if (Math.random() > 0.1) {
          resolve({
            message: `Data from ${source.name}`,
            timestamp: new Date(),
            endpoint,
            source: source.name
          } as T);
        } else {
          reject(new Error("Simulated API failure"));
        }
      }, Math.min(totalDelay, timeoutMs));
      
      // ๐Ÿงน Handle abort
      signal.addEventListener('abort', () => {
        clearTimeout(timeoutId);
        reject(new Error("Request aborted"));
      });
      
      // โฐ Handle timeout
      setTimeout(() => {
        if (!signal.aborted) {
          reject(new Error("Request timeout"));
        }
      }, timeoutMs);
    });
  }
  
  // ๐Ÿ’พ Cache management
  private cacheResult(key: string, data: T): void {
    this.cache.set(key, {
      data,
      timestamp: Date.now()
    });
  }
  
  private getCachedData(key: string): T | null {
    const cached = this.cache.get(key);
    if (!cached) return null;
    
    const age = Date.now() - cached.timestamp;
    if (age > this.cacheMaxAge) {
      this.cache.delete(key);
      return null;
    }
    
    return cached.data;
  }
  
  // ๐Ÿงน Cleanup active requests
  private cleanupActiveRequests(): void {
    this.activeControllers.forEach(controller => {
      controller.abort();
    });
    this.activeControllers.clear();
  }
  
  // ๐Ÿ“Š Get performance metrics
  getMetrics(): Map<string, SourceMetrics> {
    return new Map(this.metrics);
  }
  
  // ๐ŸŽฏ Get best performing source
  getBestSource(): DataSource | null {
    let bestSource: DataSource | null = null;
    let bestScore = -1;
    
    for (const source of this.sources) {
      const metrics = this.metrics.get(source.name);
      if (!metrics || metrics.totalRequests === 0) continue;
      
      const successRate = metrics.successCount / metrics.totalRequests;
      const avgTime = metrics.avgResponseTime || 1000;
      
      // ๐Ÿ† Score = success rate / avg response time
      const score = successRate / (avgTime / 1000);
      
      if (score > bestScore) {
        bestScore = score;
        bestSource = source;
      }
    }
    
    return bestSource;
  }
}

// ๐ŸŽฎ Demo the multi-source aggregator
async function demonstrateMultiSourceAggregator() {
  const sources: DataSource[] = [
    { name: "Primary API", url: "https://api.example.com", priority: 5, emoji: "๐ŸŽฏ" },
    { name: "Backup API", url: "https://backup.example.com", priority: 3, emoji: "๐Ÿ›ก๏ธ" },
    { name: "CDN", url: "https://cdn.example.com", priority: 4, emoji: "โšก" },
    { name: "Edge Cache", url: "https://edge.example.com", priority: 2, emoji: "๐ŸŒ" }
  ];
  
  const aggregator = new MultiSourceAggregator<any>(sources, 30000);
  
  try {
    // ๐Ÿš€ Test multiple requests
    for (let i = 1; i <= 3; i++) {
      console.log(`\n๐Ÿ”„ Request ${i}:`);
      const result = await aggregator.fetchData(`/api/data/${i}`, 1500);
      
      console.log(`โœ… Success: ${result.source.emoji} ${result.source.name}`);
      console.log(`๐Ÿ“Š Response time: ${result.responseTime}ms`);
      console.log(`๐Ÿ’พ From cache: ${result.fromCache ? 'Yes' : 'No'}`);
    }
    
    // ๐Ÿ“ˆ Show performance metrics
    console.log(`\n๐Ÿ“Š Performance Metrics:`);
    const metrics = aggregator.getMetrics();
    metrics.forEach((metric, sourceName) => {
      const successRate = metric.totalRequests > 0 
        ? (metric.successCount / metric.totalRequests * 100).toFixed(1)
        : '0';
      
      console.log(`  ${sourceName}:`);
      console.log(`    ๐Ÿ“ˆ Success rate: ${successRate}%`);
      console.log(`    โฑ๏ธ Avg response: ${metric.avgResponseTime.toFixed(0)}ms`);
      console.log(`    ๐Ÿ”ข Total requests: ${metric.totalRequests}`);
    });
    
    // ๐Ÿ† Show best performing source
    const bestSource = aggregator.getBestSource();
    if (bestSource) {
      console.log(`\n๐Ÿ† Best performing source: ${bestSource.emoji} ${bestSource.name}`);
    }
    
  } catch (error) {
    console.error("๐Ÿ’ฅ Aggregator demo failed:", error.message);
  }
}

// ๐Ÿš€ Run the demo
demonstrateMultiSourceAggregator();

๐ŸŽ“ Key Takeaways

Youโ€™ve mastered Promise.race! Hereโ€™s what you can now do:

  • โœ… Race promises effectively for fastest responses and timeouts ๐Ÿ’ช
  • โœ… Handle rejection scenarios and implement smart fallbacks ๐Ÿ›ก๏ธ
  • โœ… Build competitive async systems with proper cleanup ๐ŸŽฏ
  • โœ… Use advanced patterns like progressive racing and metrics ๐Ÿš€
  • โœ… Avoid common pitfalls like memory leaks and type issues ๐Ÿ›

Remember: Promise.race is perfect when you need the fastest result, but always plan for what happens when the winner fails! ๐Ÿค

๐Ÿค Next Steps

Congratulations! ๐ŸŽ‰ Youโ€™ve mastered Promise.race in TypeScript!

Hereโ€™s what to explore next:

  1. ๐Ÿ’ป Practice with the multi-source aggregator exercise above
  2. ๐Ÿ—๏ธ Build a real-time performance monitoring system
  3. ๐Ÿ“š Move on to our next tutorial: โ€œPromise.allSettled: Handling Mixed Resultsโ€
  4. ๐ŸŒŸ Explore advanced patterns like Promise scheduling and queuing

Remember: The fastest doesnโ€™t always win - but with proper fallbacks, youโ€™ll always have a winner! ๐Ÿš€


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