Domů O mně Služby Portfolio Učím Blog Kontakt
← Zpět na učím

07.07 Promise.race

Výuka

Proč Promise.race?

Promise.all() čeká na VŠECHNY Promises. Ale co když potřebuješ jen PRVNÍ výsledek?

Příklady kdy potřebuješ "první, kdo doběhne":

  1. Timeout - "Zkus načíst data, ale max 3 sekundy"
  2. Fallback - "Zkus primární API, pokud je pomalé, použij záložní"
  3. Fastest server - "Pošli request na 3 servery, použij nejrychlejší odpověď"
  4. User cancellation - "Čekej na data, dokud user neklikne Cancel"

Promise.race() = Závod (race) - první, kdo dojde do cíle, vyhrává!

Představ si to jako:

Máš 3 donášky jídla:

  • Uber Eats
  • Wolt
  • Bolt Food

Objednáš VŠECHNY TŘI najednou a čekáš, KDO DORAZÍ PRVNÍ.

Jakmile první dorazí → bereš jídlo, ostatní dvě donášky IGNORUJEŠ.

Promise.race([
  uberEats(),  // 15 minut
  wolt(),      // 10 minut ← VYHRÁVÁ!
  boltFood()   // 20 minut
])
.then((food) => {
  console.log('Jídlo od:', food);  // 'Jídlo od: Wolt'
  // Wolt dorazil první → vyhrává
  // Uber a Bolt se ignorují (i když pak dorazí)
});

Jak Promise.race() funguje?

Signatura:

Promise.race([promise1, promise2, promise3, ...])
  .then((firstResult) => {
    // firstResult = výsledek PRVNÍHO doběhlého Promise
  });

Pravidla:

  1. Vstup: Pole Promises
  2. Spustí všechny paralelně (jako Promise.all())
  3. Čeká na PRVNÍ, který skončí (fulfilled nebo rejected!)
  4. Výstup: Promise, který se vyřeší s výsledkem prvního dokončeného Promise

Důležité vlastnosti:

Paralelní běh - Všechny Promises běží současně ✅ First wins - První dokončený (fulfilled ČI rejected) vyhrává ✅ Ignoruje ostatní - Ostatní Promises se dokončí, ale jejich výsledky se ignorují ⚠️ Prázdné pole - Promise.race([]) NIKDY neskončí - visí napořád! ⚠️

Srovnání s Promise.all():

Promise.all() Promise.race()
Čeká na VŠECHNY Čeká na PRVNÍ
Vrací pole výsledků Vrací jeden výsledek
Fail-fast (první chyba) Fail-fast (první cokoliv)
Prázdné pole → resolved [] Prázdné pole → NIKDY ⚠️
"Gate" pattern "Latch" pattern

Schéma:

Promise.race([p1, p2, p3])
       
       ├─ p1 (5s) ───────────┐
       ├─ p2 (2s) ────┐       Běží paralelně
       └─ p3 (10s) ─────────────┐
                             
        (p2 skončí první!)    
                             
Promise<result2>           (ignoruje) (ignoruje)

Latch pattern (Západka)

Promise.race() implementuje "latch" pattern:

Představ si západku na dveřích:

  • První, kdo přijde → otevře západku → dveře se otevřou
  • Ostatní → dveře už jsou otevřené → jejich klíč se ignoruje
Promise.race([
  slowPromise(),   // 10s
  fastPromise(),   // 1s  ← První otevře "západku"
  slowPromise2()   // 5s
])
.then(() => {
  console.log('Dveře otevřené!');
  // fastPromise otevřel západku
  // slowPromise a slowPromise2 se ignorují
});

Fulfillment vs Rejection

Promise.race() reaguje na PRVNÍ vyřešení - ať je to úspěch nebo chyba!

Promise.race([
  Promise.reject('Chyba!'),     // Skončí první → VYHRÁVÁ!
  Promise.resolve('Úspěch')     // Skončí druhý → ignoruje se
])
.catch((error) => {
  console.error(error);  // 'Chyba!'
  // První Promise byl rejected → celý race() rejected
});

Metafora:

Závodí 3 běžci:
Běžec 1 spadne po 100m  "SELHAL PRVNÍ"  VYHRÁVÁ (jako rejected)
Běžec 2 doběhne po 200m  ignoruje se
Běžec 3 doběhne po 300m  ignoruje se

Promise.race() hlásí: "Běžec 1 vyhrál (tím, že spadl první)"

Timeout pattern - Nejčastější použití!

Klasický příklad: "Zkus načíst data, ale max 3 sekundy"

function timeout(ms) {
  return new Promise((_, reject) => {
    setTimeout(() => reject(new Error('Timeout!')), ms);
  });
}

Promise.race([
  fetchData(),      // Může trvat dlouho
  timeout(3000)     // Max 3 sekundy
])
.then((data) => {
  console.log('Data načtena včas!', data);
})
.catch((error) => {
  console.error('Timeout nebo chyba:', error.message);
});

Co se stalo?

  • fetchData() a timeout(3000) běží paralelně
  • Pokud fetchData() doběhne do 3s → fulfilled → data se zpracují
  • Pokud timeout doběhne první → rejected → chyba se zachytí

Kdy použít Promise.race()?

Použij Promise.race() když:

  • Potřebuješ první výsledek (rychlost je priorita)
  • Implementuješ timeout pro async operace
  • Potřebuješ fallback (záložní řešení)
  • Čekáš na user akci (např. Cancel button)
  • Soutěžíš mezi více zdroji (např. různé API)

Nepoužívej když:

  • Potřebuješ všechny výsledky (použij Promise.all())
  • Záleží ti na pořadí dokončení (ne jen na prvním)
  • Prázdné pole (nikdy neskončí!)

Klíčové koncepty

  • Latch pattern - První otevře západku, ostatní se ignorují
  • First wins - První dokončený (fulfilled ČI rejected) vyhrává
  • Timeout pattern - Nejčastější use case (race mezi operací a timeoutem)
  • Paralelní běh - Všechny Promises běží současně
  • Ignoruje ostatní - Ostatní výsledky se zahodí (ale Promises se dokončí!)

JavaScript

Základní použití Promise.race()

// Tři Promises s různými časy
function delay(ms, value) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(value), ms);
  });
}

Promise.race([
  delay(3000, 'Pomalý'),
  delay(1000, 'Rychlý'),   // ← VYHRÁVÁ!
  delay(5000, 'Nejpomalejší')
])
.then((result) => {
  console.log('Vyhrál:', result);  // 'Vyhrál: Rychlý'
});

// Výstup (po 1s):
// Vyhrál: Rychlý

Co se stalo?

  • Všechny tři Promises běžely paralelně
  • delay(1000, 'Rychlý') skončil první → vyhrál
  • Ostatní dva se dokončily později, ale jejich výsledky se ignorovaly

Rejection vs Fulfillment

Promise.race([
  new Promise((_, reject) => setTimeout(() => reject('Chyba!'), 100)),
  new Promise(resolve => setTimeout(() => resolve('OK'), 200))
])
.then((result) => {
  console.log('Nikdy se nedostaneš sem');
})
.catch((error) => {
  console.error('První skončil s chybou:', error);  // 'První skončil s chybou: Chyba!'
});

// Výstup (po 100ms):
// První skončil s chybou: Chyba!

Co se stalo?

  • První Promise (rejected) skončil po 100ms → vyhrál
  • Druhý Promise (fulfilled) skončil po 200ms → ignoroval se

Timeout pattern

function fetchData() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('Data načtena');
    }, 5000);  // Trvá 5 sekund
  });
}

function timeout(ms) {
  return new Promise((_, reject) => {
    setTimeout(() => {
      reject(new Error(`Timeout po ${ms}ms`));
    }, ms);
  });
}

// Timeout 3 sekundy
Promise.race([
  fetchData(),
  timeout(3000)
])
.then((data) => {
  console.log('Úspěch:', data);
})
.catch((error) => {
  console.error('Chyba:', error.message);  // 'Chyba: Timeout po 3000ms'
});

// Výstup (po 3s):
// Chyba: Timeout po 3000ms

Co se stalo?

  • fetchData() trvá 5s
  • timeout(3000) skončí po 3s → vyhraje → rejected
  • fetchData() se dokončí po 5s, ale výsledek se ignoruje

Timeout pattern - Úspěch

// Tentokrát data dorazí včas
function fetchDataFast() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('Data načtena rychle');
    }, 1000);  // Trvá 1 sekundu
  });
}

Promise.race([
  fetchDataFast(),
  timeout(3000)
])
.then((data) => {
  console.log('Úspěch:', data);  // 'Úspěch: Data načtena rychle'
})
.catch((error) => {
  console.error('Timeout:', error.message);
});

// Výstup (po 1s):
// Úspěch: Data načtena rychle

Co se stalo?

  • fetchDataFast() skončil po 1s → vyhrál → fulfilled
  • timeout(3000) skončí po 3s, ale ignoruje se

Fallback pattern

// Zkus primární API, pokud je pomalé, použij fallback
function fetchFromPrimaryAPI() {
  return new Promise((resolve) => {
    setTimeout(() => resolve('Data z primárního API'), 5000);
  });
}

function fetchFromFallbackAPI() {
  return new Promise((resolve) => {
    setTimeout(() => resolve('Data ze záložního API'), 1000);
  });
}

Promise.race([
  fetchFromPrimaryAPI(),
  fetchFromFallbackAPI()  // Rychlejší → vyhrává
])
.then((data) => {
  console.log('Dostali jsme:', data);  // 'Dostali jsme: Data ze záložního API'
});

// Výstup (po 1s):
// Dostali jsme: Data ze záložního API

Prázdné pole - POZOR!

// ⚠️ NIKDY NESKONČÍ!
Promise.race([])
  .then(() => {
    console.log('Nikdy se nevypíše');
  });

// ... visí napořád ...

Proč?

  • Žádný Promise → nikdo nemůže "vyhrát"
  • Promise zůstane Pending napořád
  • VŽDY kontroluj, že pole není prázdné!

Reálný příklad - User cancellation

// User může zrušit operaci kliknutím na tlačítko
function fetchWithCancellation(button) {
  // Promise pro fetch
  const fetchPromise = fetch('/api/data')
    .then(res => res.json());

  // Promise pro Cancel button
  const cancelPromise = new Promise((_, reject) => {
    button.addEventListener('click', () => {
      reject(new Error('Zrušeno uživatelem'));
    }, { once: true });
  });

  // Race mezi fetch a cancel
  return Promise.race([fetchPromise, cancelPromise]);
}

// Použití
const cancelButton = document.querySelector('#cancel');

fetchWithCancellation(cancelButton)
  .then((data) => {
    console.log('Data načtena:', data);
  })
  .catch((error) => {
    console.error(error.message);  // 'Zrušeno uživatelem'
  });

Fastest server

// Pošli request na 3 servery, použij nejrychlejší odpověď
Promise.race([
  fetch('https://api1.example.com/data'),
  fetch('https://api2.example.com/data'),
  fetch('https://api3.example.com/data')
])
.then(response => response.json())
.then(data => {
  console.log('Nejrychlejší server odpověděl:', data);
})
.catch(error => {
  console.error('První server selhal:', error);
});

TypeScript

TypeScript přidává typovou bezpečnost pro Promise.race().

Typované Promise.race()

// TypeScript odvodí společný typ
Promise.race([
  Promise.resolve(42),           // Promise<number>
  Promise.resolve(100),          // Promise<number>
  Promise.resolve(5)             // Promise<number>
])
.then((num: number) => {
  // TS ví, že num je number
  console.log(num.toFixed(2));  // ✅
});

TypeScript odvodí typ:

Promise.race([Promise<T1>, Promise<T2>, ...]) → Promise<T1 | T2 | ...>

Různé typy - Union type

// Různé typy → Union type
Promise.race([
  Promise.resolve(42),         // Promise<number>
  Promise.resolve('hello')     // Promise<string>
])
.then((result: number | string) => {
  // TS ví, že result je number NEBO string
  if (typeof result === 'number') {
    console.log(result.toFixed(2));  // ✅ V tomto bloku je number
  } else {
    console.log(result.toUpperCase());  // ✅ V tomto bloku je string
  }
});

Timeout pattern s typy

function timeout<T = never>(ms: number): Promise<T> {
  return new Promise((_, reject) => {
    setTimeout(() => reject(new Error(`Timeout po ${ms}ms`)), ms);
  });
}

interface User {
  id: number;
  name: string;
}

function fetchUser(): Promise<User> {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ id: 1, name: 'Jan' });
    }, 2000);
  });
}

// TS odvodí Promise<User>
Promise.race([
  fetchUser(),
  timeout<User>(3000)
])
.then((user: User) => {
  console.log(user.name);  // ✅ TS ví, že je to User
})
.catch((error: Error) => {
  console.error(error.message);
});

Generická funkce s timeout

// Generická funkce pro timeout
function withTimeout<T>(
  promise: Promise<T>,
  ms: number
): Promise<T> {
  return Promise.race([
    promise,
    timeout<T>(ms)
  ]);
}

// Použití
withTimeout(fetchUser(), 3000)
  .then((user: User) => {
    console.log(user.name);  // ✅ TS ví typ
  });

Error handling s typy

class TimeoutError extends Error {
  constructor(ms: number) {
    super(`Timeout po ${ms}ms`);
    this.name = 'TimeoutError';
  }
}

function timeout<T>(ms: number): Promise<T> {
  return new Promise((_, reject) => {
    setTimeout(() => reject(new TimeoutError(ms)), ms);
  });
}

Promise.race([fetchUser(), timeout<User>(3000)])
  .catch((error: unknown) => {
    if (error instanceof TimeoutError) {
      console.error('Timeout!', error.message);
    } else if (error instanceof Error) {
      console.error('Jiná chyba:', error.message);
    }
  });

Rozdíl JS vs TS

JavaScript:

  • Promise.race() bez typů
  • Nevíš, jaký typ dostaneš v .then()
  • Pokud různé typy → musíš kontrolovat za běhu
  • Chyby až za běhu

TypeScript:

  • Promise - union type všech možných výsledků
  • Autocomplete v IDE
  • Type narrowing pomocí typeof nebo instanceof
  • Kontrola typů při kompilaci

Příklad:

// JS - nevíš co dostaneš
Promise.race([fetchUser(), fetchPosts()])
  .then((result) => {
    console.log(result.name);  // Může selhat za běhu!
  });

// TS - víš možné typy
Promise.race([fetchUser(), fetchPosts()])
  .then((result: User | Post[]) => {
    if (Array.isArray(result)) {
      // TS ví: result je Post[]
      console.log(result[0].title);
    } else {
      // TS ví: result je User
      console.log(result.name);
    }
  });

Tip

💡 Timeout pattern - Helper funkce:

// ✅ Vytvoř reusable helper
function withTimeout(promise, ms) {
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => reject(new Error('Timeout')), ms);
  });

  return Promise.race([promise, timeoutPromise]);
}

// Použití
withTimeout(fetchData(), 3000)
  .then(handleData)
  .catch(handleError);

💡 Promise.race vs Promise.all - Kdy co:

// ❌ NELZE: Kroky závisí na sobě
Promise.race([fetchUser(), fetchSettings()])  // settings potřebuje user!

// ✅ Promise.all: Potřebuješ VŠECHNY výsledky
Promise.all([fetchUser(), fetchPosts()])

// ✅ Promise.race: Potřebuješ PRVNÍ výsledek
Promise.race([primaryAPI(), fallbackAPI()])

💡 Prázdné pole = NIKDY neskončí:

// ⚠️ POZOR!
Promise.race([])  // Visí napořád!

// ✅ Vždy kontroluj
if (promises.length === 0) {
  return Promise.reject(new Error('Žádné promises'));
}
return Promise.race(promises);

💡 Fallback cascade (kaskáda záloh):

// Zkus API v pořadí: primární → sekundární → terciární
function fetchWithFallbacks() {
  return Promise.race([
    fetch('/primary-api'),
    delay(1000).then(() => fetch('/secondary-api')),
    delay(2000).then(() => fetch('/tertiary-api'))
  ]);
}

💡 Race vs allSettled:

// Promise.race() - První výsledek (success nebo error)
Promise.race([p1, p2, p3])  // První, kdo skončí

// Promise.allSettled() - VŠECHNY výsledky (i errors)
Promise.allSettled([p1, p2, p3])  // Čeká na všechny, ale neselže

💡 Cleanup pattern:

// Co s Promises, které "prohrály"?
// Odpověď: Dokončí se, ale ignorují se.
// Pokud potřebuješ cleanup:

let cleanup = null;

Promise.race([
  new Promise(resolve => {
    const timer = setTimeout(() => resolve('OK'), 5000);
    cleanup = () => clearTimeout(timer);
  }),
  timeout(1000)
])
.finally(() => {
  if (cleanup) cleanup();  // Vyčisti zdroje
});

Kvíz

Které výroky o Promise.race() jsou pravdivé?

- Promise.race() vrací JEDEN výsledek (prvního, kdo skončil), NE pole! Pole vrací Promise.all().

- First wins - První Promise, který se vyřeší (ať fulfilled nebo rejected), určuje výsledek celého race()

- Pokud první Promise je rejected, celý Promise.race() je okamžitě rejected s tou chybou

- Promise.race([]) NIKDY neskončí - visí v Pending stavu napořád! (Je to bug v ES6 spec, ale zůstalo to kvůli kompatibilitě)

🎯 Závěrečný projekt

Po dokončení všech 8 dílů vytvoříte jednoduchou Todо aplikaci v čistém JavaScriptu. Naučíte se, jak aplikovat vše, co jste se naučili, na reálný projekt.

Zobrazit podrobnosti projektu →

Připraveni začít?

Zaregistrujte se a získejte přístup ke všem dílům tohoto seriálu

Kontaktujte mě

Informace o seriálu

Obtížnost

Délka

Cca 480 minut

Počet videí

8 videí + projekty

Certifikát

Po dokončení obdržíte certifikát


Lekce v této sekci


Struktura lekcí (souborový strom)

06. Typescript specifika
  • v přípravě
08. Moduly tridy
  • v přípravě
09. React zaklady
  • v přípravě
10. React hooks
  • v přípravě
12. Nextjs server
  • v přípravě
13. Databaze auth
  • v přípravě
14. Nextjs pokrocile
  • v přípravě