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

07.05 Then a Catch

Výuka

Proč Then a Catch?

V minulé lekci jsme viděli, že Promise je stvrzenka za budoucí hodnotu. Ale jak tu hodnotu použít, když přijde? A co když něco selže?

Představ si to jako:

Máš stvrzenku za burger (Promise). Potřebuješ způsob, jak říct:

  • "Až bude burger hotový, zavolej mi a já ho sním".then()
  • "Pokud burgery dojdou, zavolej mi a já si objednám něco jiného".catch()

Promise sám o sobě nedělá nic! Je to jen objekt reprezentující budoucnost. Potřebuješ zaregistrovat callbacky, které se zavolají, až Promise skončí.

const promise = orderBurger();  // Vrátí Promise

// Promise sám nedělá nic viditelného!
// Musíš říct, CO dělat s výsledkem:

promise.then((burger) => {
  eat(burger);  // Co dělat při úspěchu
});

promise.catch((error) => {
  orderPizza();  // Co dělat při chybě
});

Jak .then() funguje?

.then() registruje dva callbacky:

  1. Fulfillment handler - zavolá se, když Promise uspěje
  2. Rejection handler (volitelný) - zavolá se, když Promise selže
promise.then(
  (value) => { /* úspěch */ },  // fulfillment handler
  (error) => { /* chyba */ }     // rejection handler (volitelné)
);

Klíčové:

  • .then() vrací NOVÝ Promise → můžeš řetězit!
  • Hodnota, kterou vrátíš z .then(), stane se hodnotou dalšího Promise v řetězu
Promise A ---> .then() ---> Promise B ---> .then() ---> Promise C
                                          
               vrací Promise B             vrací Promise C

Jak .catch() funguje?

.catch() je zkratka pro .then(null, rejectionHandler):

// Tyto dva zápisy jsou IDENTICKÉ:
promise.then(null, (err) => console.error(err));
promise.catch((err) => console.error(err));

Proč .catch() existuje?

  • Čitelnější kód
  • Idiomatický způsob pro error handling
  • Běžná konvence v Promise řetězech

Důležité: .catch() také vrací nový Promise, takže můžeš pokračovat v řetězení!

promise
  .then(value => processValue(value))
  .catch(err => console.error(err))  // Zachytí chybu
  .then(() => console.log('Pokračuji dál'));  // Spustí se i po chybě!

Chaining (Řetězení) - Nejdůležitější koncept!

Každý .then() vrací NOVÝ Promise. To umožňuje lineární zápis místo callback hell:

// ❌ Callback hell (vnořené)
fetchUser((err, user) => {
  if (err) return handleError(err);
  fetchSettings(user.id, (err, settings) => {
    if (err) return handleError(err);
    fetchArticles(settings.limit, (err, articles) => {
      if (err) return handleError(err);
      console.log(articles);
    });
  });
});

// ✅ Promise chaining (lineární!)
fetchUser()
  .then(user => fetchSettings(user.id))
  .then(settings => fetchArticles(settings.limit))
  .then(articles => console.log(articles))
  .catch(err => handleError(err));  // Jedna catch pro VŠE!

Jak to funguje?

  1. fetchUser() vrátí Promise → zavolá se .then() s user
  2. Vrátíš fetchSettings(user.id) (což je Promise) → automaticky se rozbalí
  3. fetchSettings skončí → zavolá se další .then() s settings
  4. A tak dále...

Magická vlastnost: Pokud vrátíš Promise z .then(), následující .then() počká, až se ten Promise vyřeší!

Promise.resolve(21)
  .then(v => {
    console.log(v);  // 21

    // Vrátíš Promise, který se vyřeší za 1 sekundu
    return new Promise((resolve) => {
      setTimeout(() => resolve(v * 2), 1000);
    });
  })
  .then(v => {
    console.log(v);  // 42 (po 1 sekundě!)
  });

Error Propagation (Šíření chyb)

Chyby se propagují řetězem dolů, dokud nenarazí na rejection handler (.catch() nebo druhý parametr .then()):

Promise.resolve(42)
  .then(v => {
    throw new Error('Oops!');  // Chyba!
  })
  .then(v => {
    console.log('Nikdy se nezavolá');  // Přeskočí se
  })
  .then(v => {
    console.log('Taky ne');  // Přeskočí se
  })
  .catch(err => {
    console.error('Chytil jsem chybu!', err.message);  // 'Oops!'
  });

Co se stalo?

  1. První .then() vyhodil chybu
  2. Promise z prvního .then() se stal rejected
  3. Další .then() callbacky se přeskočily (mají jen fulfillment handler)
  4. .catch() chybu zachytil

Klíčové pravidlo:

  • Pokud .then() nemá rejection handler, použije se defaultní, který chybu propaguje dál:
    // Default rejection handler:
    (err) => { throw err; }  // Jen přehází chybu dál
    

Default Handlers

Pokud vynecháš handler v .then(), použije se defaultní:

1. Default fulfillment handler (když vynecháš první parametr):

// Default fulfillment handler:
(value) => value  // Jen předá hodnotu dál

Příklad:

Promise.resolve(42)
  .then(null, (err) => console.error(err))  // Jen rejection handler
  .then(v => console.log(v));  // 42 - hodnota prošla skrz!

2. Default rejection handler (když vynecháš druhý parametr):

// Default rejection handler:
(err) => { throw err; }  // Propaguje chybu dál

Příklad:

Promise.reject('Chyba!')
  .then(v => console.log(v))  // Žádný rejection handler → použije default
  .catch(err => console.error(err));  // 'Chyba!' - chyba prošla skrz

Problém: Polknuté chyby (Swallowed Errors)

Pozor! Chyby se snadno ztratí, pokud zapomeneš .catch():

Promise.resolve(42)
  .then(v => {
    console.log(v.toLowerCase());  // CHYBA! Number nemá toLowerCase()
  });

// Chyba se "polkne" - nikdo ji nevidí! 😱

Proč?

  • Chyba v .then() odmítne Promise, který .then() vrátil
  • Ale my jsme ten Promise nezachytili!
  • Chyba zmizí do prázdna...

Řešení: VŽDY končit řetěz .catch()

Promise.resolve(42)
  .then(v => {
    console.log(v.toLowerCase());  // CHYBA!
  })
  .catch(err => {
    console.error('Zachytil jsem chybu!', err);  // ✅ Vidíme ji!
  });

Best practice:

fetchData()
  .then(processData)
  .then(saveData)
  .catch(handleAllErrors);  // Zachytí chyby ze všech kroků!

Klíčové koncepty

  • .then(onFulfilled, onRejected) - Zaregistruje callbacky pro úspěch/chybu
  • .catch(onRejected) - Zkratka pro .then(null, onRejected)
  • Chaining - Každý .then() / .catch() vrací nový Promise
  • Value passing - Hodnota z return se stane hodnotou dalšího Promise
  • Promise unwrapping - Pokud vrátíš Promise, automaticky se rozbalí
  • Error propagation - Chyby se šíří řetězem dolů k první .catch()
  • Default handlers - Vynechaný handler → použije se default (pass-through nebo rethrow)

JavaScript

Základní použití .then()

// Promise, který se vyřeší za 1 sekundu
const promise = new Promise((resolve) => {
  setTimeout(() => {
    resolve('Hotovo!');
  }, 1000);
});

// Zaregistruj fulfillment handler
promise.then((value) => {
  console.log(value);  // 'Hotovo!' (po 1s)
});

console.log('Pokračuji dál...');  // Vypíše se HNED

// Výstup:
// Pokračuji dál...
// (po 1s): Hotovo!

Co se stalo?

  1. promise je Pending (čeká se na timeout)
  2. .then() zaregistruje callback - uloží si ho pro později
  3. Program pokračuje (neblokuje!)
  4. Po 1 sekundě se Promise vyřeší → callback se zavolá

.then() s oběma handlery

const promise = new Promise((resolve, reject) => {
  const success = Math.random() > 0.5;

  setTimeout(() => {
    if (success) {
      resolve('Úspěch!');
    } else {
      reject('Chyba!');
    }
  }, 1000);
});

promise.then(
  (value) => {
    console.log('✅', value);  // Úspěch!
  },
  (error) => {
    console.error('❌', error);  // Chyba!
  }
);

// Buď:
// ✅ Úspěch!
// Nebo:
// ❌ Chyba!

Chaining - Předávání hodnot

Promise.resolve(10)
  .then((num) => {
    console.log('Krok 1:', num);  // 10
    return num * 2;  // Vrátíš hodnotu
  })
  .then((num) => {
    console.log('Krok 2:', num);  // 20
    return num + 5;
  })
  .then((num) => {
    console.log('Krok 3:', num);  // 25
    return num / 5;
  })
  .then((num) => {
    console.log('Finální:', num);  // 5
  });

// Výstup:
// Krok 1: 10
// Krok 2: 20
// Krok 3: 25
// Finální: 5

Co se stalo?

  • Každý .then() vrací hodnotu
  • Ta hodnota se stane hodnotou dalšího Promise
  • Lineární tok dat! ✅

Chaining - Async kroky

function delay(ms, value) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(value), ms);
  });
}

console.log('Start');

delay(1000, 'Krok 1')
  .then((result) => {
    console.log(result);  // 'Krok 1' (po 1s)
    return delay(500, 'Krok 2');  // Vrátíš Promise!
  })
  .then((result) => {
    console.log(result);  // 'Krok 2' (po další 0.5s)
    return delay(300, 'Krok 3');
  })
  .then((result) => {
    console.log(result);  // 'Krok 3' (po další 0.3s)
    console.log('Konec');
  });

// Výstup:
// Start
// (po 1s): Krok 1
// (po 0.5s): Krok 2
// (po 0.3s): Krok 3
// Konec

Co se stalo?

  • Vrátíš-li Promise z .then(), další .then() počká, až se vyřeší!
  • Async operace za sebou, ale lineární zápis (ne callback hell)

Error handling s .catch()

Promise.resolve('OK')
  .then((value) => {
    console.log(value);  // 'OK'
    throw new Error('Něco se pokazilo!');
  })
  .then((value) => {
    console.log('Nikdy se nezavolá');  // Přeskočí se
  })
  .catch((error) => {
    console.error('Chyba:', error.message);  // 'Něco se pokazilo!'
    return 'Opraveno';  // Můžeš vrátit hodnotu!
  })
  .then((value) => {
    console.log('Pokračuji:', value);  // 'Opraveno'
  });

// Výstup:
// OK
// Chyba: Něco se pokazilo!
// Pokračuji: Opraveno

Co se stalo?

  1. První .then() vyhodil chybu → Promise rejected
  2. Druhý .then() se přeskočil (žádný rejection handler)
  3. .catch() chybu zachytil a vrátil 'Opraveno'
  4. Promise z .catch() se stal Fulfilled s hodnotou 'Opraveno'
  5. Další .then() pokračoval normálně

Reálný příklad - HTTP request chain

// Simulace HTTP requestu
function fetchUser(id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (id > 0) {
        resolve({ id, name: 'Jan', settingsId: 42 });
      } else {
        reject(new Error('Neplatné ID'));
      }
    }, 500);
  });
}

function fetchSettings(settingsId) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ theme: 'dark', lang: 'cs' });
    }, 300);
  });
}

// Promise chain
fetchUser(1)
  .then((user) => {
    console.log('Uživatel:', user.name);
    return fetchSettings(user.settingsId);  // Vrátíš Promise
  })
  .then((settings) => {
    console.log('Nastavení:', settings.theme);
  })
  .catch((error) => {
    console.error('Chyba:', error.message);
  });

// Výstup (po ~800ms):
// Uživatel: Jan
// Nastavení: dark

Problém: Polknutá chyba

// ❌ CHYBA SE ZTRATÍ!
Promise.resolve(42)
  .then((num) => {
    return num.toUpperCase();  // CHYBA! Number nemá toUpperCase()
  });

// ... nic! Chyba se "polkla", nikdo ji neuvidí 😱

Oprava:

// ✅ Chyba se zachytí
Promise.resolve(42)
  .then((num) => {
    return num.toUpperCase();  // CHYBA!
  })
  .catch((err) => {
    console.error('Zachyceno:', err.message);  // ✅
  });

TypeScript

TypeScript přidává typovou bezpečnost pro Promise hodnoty a error handling.

Typované .then() callbacky

// Promise<number>
const promise: Promise<number> = Promise.resolve(42);

promise.then((value: number) => {
  console.log(value.toFixed(2));  // ✅ TS ví, že value je number
  // console.log(value.toUpperCase());  // ❌ Error
});

TypeScript automaticky odvodí typy:

Promise.resolve(42)
  .then((value) => {
    // TS ví, že value: number
    return value * 2;
  })
  .then((value) => {
    // TS ví, že value: number (vrátil jsi number)
    console.log(value);
  });

Chaining s různými typy

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

interface Settings {
  theme: string;
  lang: string;
}

function fetchUser(id: number): Promise<User> {
  return Promise.resolve({ id, name: 'Jan' });
}

function fetchSettings(userId: number): Promise<Settings> {
  return Promise.resolve({ theme: 'dark', lang: 'cs' });
}

// TS sleduje typy v celém řetězu
fetchUser(1)
  .then((user: User) => {
    console.log(user.name);  // ✅ TS ví, že user má name
    return fetchSettings(user.id);  // Vrací Promise<Settings>
  })
  .then((settings: Settings) => {
    console.log(settings.theme);  // ✅ TS ví, že settings má theme
  });

Error handling s typy

// Typ pro chybu
class ApiError extends Error {
  constructor(public code: number, message: string) {
    super(message);
  }
}

function fetchData(): Promise<string> {
  return new Promise((resolve, reject) => {
    const success = Math.random() > 0.5;

    if (success) {
      resolve('Data loaded');
    } else {
      reject(new ApiError(404, 'Not found'));
    }
  });
}

fetchData()
  .then((data: string) => {
    console.log(data.toUpperCase());  // ✅
  })
  .catch((error: Error) => {
    // TS ví, že error je Error (nebo jeho podtyp)
    console.error(error.message);

    if (error instanceof ApiError) {
      console.error('API Error code:', error.code);
    }
  });

Generické Promise funkce

// Generická funkce vracející Promise
function wrapInPromise<T>(value: T): Promise<T> {
  return Promise.resolve(value);
}

wrapInPromise(42)
  .then((num) => {
    // TS ví, že num: number
    console.log(num * 2);
  });

wrapInPromise('hello')
  .then((str) => {
    // TS ví, že str: string
    console.log(str.toUpperCase());
  });

Typování .catch() handleru

// .catch() vždy dostává unknown (nebo any v některých TS verzích)
Promise.reject(new Error('Oops'))
  .catch((error: unknown) => {
    // Musíš zkontrolovat typ před použitím
    if (error instanceof Error) {
      console.error(error.message);  // ✅ Bezpečné
    }
  });

Rozdíl JS vs TS

JavaScript:

  • .then() a .catch() bez typů
  • Nevíš, jaký typ hodnoty dostaneš v callbacku
  • Chyby až za běhu
  • Žádná kontrola, co můžeš s hodnotou dělat

TypeScript:

  • Promise - přesný typ hodnoty
  • Autocomplete v IDE pro hodnoty v .then()
  • Kontrola typů při kompilaci
  • Vynucuje správné použití hodnot

Příklad:

// JS - nevíš co dostaneš
fetchData().then((data) => {
  console.log(data.toUpperCase());  // Může selhat za běhu
});

// TS - víš přesně
fetchData().then((data: string) => {
  console.log(data.toUpperCase());  // ✅ TS ví, že je to string
  // console.log(data.toFixed());   // ❌ Error - string nemá toFixed
});

Tip

💡 .catch() vs druhý parametr .then():

// ❌ Toto NEZACHYTÍ chyby z prvního handleru!
promise.then(
  (value) => {
    throw new Error('Chyba v handleru');  // Nezachytí se!
  },
  (error) => {
    console.error(error);  // Nikdy se nezavolá
  }
);

// ✅ Toto zachytí chyby z CELÉHO řetězu
promise
  .then((value) => {
    throw new Error('Chyba v handleru');  // Zachytí se!
  })
  .catch((error) => {
    console.error(error);  // ✅ Zavolá se
  });

Pravidlo: Používej .catch() na konci řetězu, ne druhý parametr .then()!

💡 Vždy končit řetěz s .catch():

// ✅ Best practice
fetchData()
  .then(processData)
  .then(saveData)
  .then(showSuccess)
  .catch(handleError);  // Zachytí chyby ze všech kroků

💡 Recovery pattern (zotavení z chyby):

fetchPrimaryAPI()
  .catch((error) => {
    console.warn('Primary API failed, using fallback');
    return fetchFallbackAPI();  // Zkus záložní API
  })
  .then((data) => {
    console.log('Data (z primary nebo fallback):', data);
  });

💡 Chaining hodnot vs Promises:

// Hodnota → další .then() dostane hodnotu HNED
.then(() => 42)

// Promise → další .then() POČKÁ, až se Promise vyřeší
.then(() => Promise.resolve(42))

// Async funkce → vrací Promise → další .then() počká
.then(async () => 42)

💡 Default fulfillment handler (pass-through):

Promise.resolve(42)
  .then(null, (err) => console.error(err))  // Jen rejection handler
  .then((value) => console.log(value));  // 42 - hodnota "prošla skrz"

// Ekvivalentní s:
Promise.resolve(42)
  .then((v) => v, (err) => console.error(err))  // Explicitní pass-through
  .then((value) => console.log(value));

Kvíz

Které výroky o .then() a .catch() jsou pravdivé?

- Každý .then() vrací nový Promise → můžeš řetězit .then() za .then()

- .catch(fn) je přesně stejné jako .then(null, fn) - jen čitelnější syntax

- Chyby se PROPAGUJÍ dál - pokud není rejection handler, použije se default, který chybu rethrowne (přehodí dál)

- Promise unwrapping - pokud vrátíš Promise, automaticky se rozbalí a další .then() počká na jeho vyřešení

🎯 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ě