13. února 2026

Async/await

Existuje speciální syntaxe, která umožňuje pracovat s přísliby pohodlnějším způsobem. Nazývá se „async/await“. Je překvapivě snadné jí porozumět a používat ji.

Asynchronní funkce

Začněme klíčovým slovem async. To může být umístěno před funkci, například:

async function f() {
  return 1;
}

Slovo „async“ před funkcí znamená jedno jediné: funkce vždy vrátí příslib. Jiné hodnoty budou automaticky zabaleny do splněného příslibu.

Například tato funkce vrátí splněný příslib s výsledkem 1; otestujme to:

async function f() {
  return 1;
}

f().then(alert); // 1

…Mohli bychom explicitně vrátit příslib, což by bylo totéž:

async function f() {
  return Promise.resolve(1);
}

f().then(alert); // 1

Klíčové slovo async tedy zaručuje, že funkce vrátí příslib, a jiné hodnoty než přísliby do něj zabalí. Jednoduché, že? Ale to není všechno. Existuje další klíčové slovo, await, které funguje výhradně uvnitř funkcí s async a je opravdu pěkné.

Await

Syntaxe:

// funguje jen v asynchronních funkcích
let hodnota = await příslib;

Klíčové slovo await přiměje JavaScript počkat, než se příslib usadí, a vrátí jeho výsledek.

Následuje příklad s příslibem, který se splní za 1 sekundu:

async function f() {

  let příslib = new Promise((splň, zamítni) => {
    setTimeout(() => splň("hotovo!"), 1000)
  });

  let výsledek = await příslib; // čeká, než se příslib splní (*)

  alert(výsledek); // "hotovo!"
}

f();

Výkon funkce se na řádku (*) „pozastaví“ a obnoví se až tehdy, když se příslib usadí. Jeho výsledek se uloží do proměnné výsledek. Proto uvedený kód zobrazí za jednu sekundu „hotovo!“.

Zdůrazněme to: await doslova přeruší výkon funkce, dokud se příslib neusadí, a pak jej obnoví s výsledkem příslibu. To nestojí žádné zdroje CPU, protože motor JavaScriptu mezitím může vykonávat jinou práci: spouštět další skripty, ošetřovat události a podobně.

Je to jen elegantnější syntaxe získání výsledku příslibu než příslib.then. A snadněji se čte a píše.

V běžných funkcích nelze await používat

Pokud se pokusíme použít await v neasynchronní funkci, nastane syntaktická chyba:

function f() {
  let příslib = Promise.resolve(1);
  let výsledek = await příslib; // Syntaktická chyba
}

Tuto chybu můžeme získat, když zapomeneme uvést async před funkcí. Jak bylo uvedeno, await funguje jedině uvnitř funkce s async.

Vezměme nyní příklad zobrazAvatara() z kapitoly Zřetězení příslibů a přepišme jej za použití async/await:

  1. Musíme nahradit volání .then za await.
  2. Aby to fungovalo, měli bychom také označit funkci jako async.
async function zobrazAvatara() {

  // načteme náš JSON
  let odpověď = await fetch('/article/promise-chaining/user.json');
  let uživatel = await odpověď.json();

  // načteme uživatele GitHubu
  let odpověďGitHubu = await fetch(`https://api.github.com/users/${uživatel.name}`);
  let uživatelGitHubu = await odpověďGitHubu.json();

  // zobrazíme avatara
  let obrázek = document.createElement('img');
  obrázek.src = uživatelGitHubu.avatar_url;
  obrázek.className = "promise-avatar-example";
  document.body.append(obrázek);

  // počkáme 3 sekundy
  await new Promise((splň, zamítni) => setTimeout(splň, 3000));

  obrázek.remove();

  return uživatelGitHubu;
}

zobrazAvatara();

Pěkně čisté a snadno čitelné, že? Mnohem lepší než předtím.

Moderní prohlížeče povolují await na nejvyšší úrovni v modulech

V moderních prohlížečích funguje await dobře i na nejvyšší úrovni, když jsme uvnitř modulu. Moduly probereme v článku Úvod do modulů.

Například:

// předpokládáme, že tento kód běží na nejvyšší úrovni uvnitř modulu
let odpověď = await fetch('/article/promise-chaining/user.json');
let uživatel = await odpověď.json();

console.log(uživatel);

Jestliže nepoužíváme moduly nebo musíme podporovat i starší prohlížeče, pak existuje univerzální návod: zabalení do anonymní asynchronní funkce.

Například:

(async () => {
  let odpověď = await fetch('/article/promise-chaining/user.json');
  let uživatel = await odpověď.json();
  ...
})();
await přijímá „thenable“ objekty

Stejně jako příslib.then, i await nám umožňuje používat thenable objekty (ty, které obsahují volatelnou metodu then). Myšlenkou je, že objekt třetí strany nemusí být příslib, ale objekt kompatibilní s příslibem: k tomu, abychom jej použili s await, stačí, aby podporoval then.

Následuje příklad třídy Thenable; await pod ní přijímá její instance:

class Thenable {
  constructor(číslo) {
    this.číslo = číslo;
  }
  then(splň, zamítni) {
    alert(splň);
    // splnění s hodnotou this.číslo*2 za 1000 ms
    setTimeout(() => splň(this.číslo * 2), 1000); // (*)
  }
}

async function f() {
  // počká 1 sekundu, pak výsledek bude 2
  let výsledek = await new Thenable(1);
  alert(výsledek);
}

f();

Jestliže await obdrží nepříslibový objekt obsahující .then, pak tuto metodu zavolá a jako argumenty splň a zamítni jí poskytne zabudované funkce (tak, jak to učiní pro obvyklý exekutor Promise). Pak await počká, dokud nebude zavolána jedna z nich (v uvedeném příkladu se to stane na řádku (*)), a poté bude pokračovat s výsledkem.

Asynchronní třídní metody

Chceme-li deklarovat asynchronní třídní metodu, jednoduše před ni uvedeme async:

class Čekatel {
  async čekej() {
    return await Promise.resolve(1);
  }
}

new Čekatel()
  .čekej()
  .then(alert); // 1 (to je totéž jako (výsledek => alert(výsledek)))

Význam je stejný: zajišťuje, že vrácená hodnota je příslib, a umožňuje await.

Ošetřování chyb

Jestliže je příslib normálně splněn, pak await příslib vrátí výsledek. V případě zamítnutí však vyvolá chybu, tak, jako by na tomto řádku byl uveden příkaz throw.

Tento kód:

async function f() {
  await Promise.reject(new Error("Ouha!"));
}

…je stejný jako tento:

async function f() {
  throw new Error("Ouha!");
}

V reálných situacích může nějakou dobu trvat, než bude příslib zamítnut. V tom případě předtím, než await vyvolá chybu, nastane prodleva.

Tuto chybu můžeme zachytit pomocí try..catch, stejným způsobem, jako obvyklé throw:

async function f() {

  try {
    let odpověď = await fetch('http://takove-url-neni');
  } catch(chyba) {
    alert(chyba); // TypeError: failed to fetch
  }
}

f();

V případě chyby řízení skočí do bloku catch. Můžeme také zabalit více řádků:

async function f() {

  try {
    let odpověď = await fetch('/tady-neni-uzivatel');
    let uživatel = await odpověď.json();
  } catch(chyba) {
    // zachytává chyby z funkce fetch i z funkce odpověď.json
    alert(chyba);
  }
}

f();

Pokud nemáme try..catch, stane se příslib generovaný voláním asynchronní funkce f() zamítnutým. Můžeme připojit .catch, abychom to ošetřili:

async function f() {
  let odpověď = await fetch('http://takove-url-neni');
}

// f() se stane zamítnutým příslibem
f().catch(alert); // TypeError: failed to fetch // (*)

Jestliže sem zapomeneme přidat .catch, získáme neošetřenou chybu příslibu (viditelnou v konzoli). Takové chyby můžeme zachytávat pomocí globálního handleru událostí unhandledrejection, popsaného v kapitole Ošetřování chyb pomocí příslibů.

async/await a promise.then/catch

Když používáme async/await, potřebujeme .then jenom zřídka, neboť await nám čekání zpracuje. A místo .catch můžeme použít běžné try..catch. To je obvykle (ale ne vždy) vhodnější.

Avšak na nejvyšší úrovni kódu, když jsme mimo jakoukoli funkci s async, nemůžeme syntakticky použít await, takže je obvyklou praktikou přidat .then/catch pro ošetření konečného výsledku nebo vypadnuvší chyby, jako na řádku (*) v uvedeném příkladu.

async/await funguje správně s Promise.all

Když potřebujeme čekat na více příslibů, můžeme je zabalit do Promise.all a pak volat await:

// čekáme na pole výsledků
let výsledky = await Promise.all([
  fetch(url1),
  fetch(url2),
  ...
]);

V případě chyby se tato chyba vyvolá jako obvykle, z neúspěšného příslibu do Promise.all, a pak se stane výjimkou, kterou můžeme zachytit použitím try..catch okolo volání.

Shrnutí

Klíčové slovo async před funkcí má dva efekty:

  1. Způsobí, že funkce vždy vrátí příslib.
  2. Umožní v ní použít await.

Klíčové slovo await před příslibem přiměje JavaScript čekat, než se příslib usadí, a pak:

  1. Pokud je to chyba, vygeneruje se výjimka – totéž, jako by přesně na tomto místě bylo voláno throw chyba.
  2. V opačném případě vrátí výsledek.

Společně poskytují vynikající rámec pro psaní asynchronního kódu, který je snadné číst i psát.

S async/await potřebujeme psát příslib.then/catch jen zřídka, ale stále bychom neměli zapomínat, že tyto metody jsou založeny na příslibech, protože někdy (např. na nejvyšší úrovni kódu) je musíme použít. Když čekáme na mnoho úkolů současně, hodí se i Promise.all.

Úlohy

Přepište tento příklad kódu z kapitoly Zřetězení příslibů za použití async/await namísto .then/catch:

function načtiJson(url) {
  return fetch(url)
    .then(odpověď => {
      if (odpověď.status == 200) {
        return odpověď.json();
      } else {
        throw new Error(odpověď.status);
      }
    });
}

načtiJson('https://javascript.info/takovy-uzivatel-neni.json')
  .catch(alert); // Chyba: 404

Poznámky jsou pod kódem:

async function načtiJson(url) { // (1)
  let odpověď = await fetch(url); // (2)

  if (odpověď.status == 200) {
    let json = await odpověď.json(); // (3)
    return json;
  }

  throw new Error(odpověď.status);
}

načtiJson('https://javascript.info/takovy-uzivatel-neni.json')
  .catch(alert); // Chyba: 404 (4)

Poznámky:

  1. Funkce načtiJson se stává asynchronní (async).

  2. Všechna .then uvnitř jsou nahrazena za await.

  3. Můžeme vrátit return odpověď.json() místo čekání na tuto funkci, například:

    if (odpověď.status == 200) {
      return odpověď.json(); // (3)
    }

    Pak by vnější kód musel počkat pomocí await, než se tento příslib vyhodnotí. V našem případě na tom nezáleží.

  4. Chyba vyvolaná z načtiJson je ošetřena pomocí .catch. Nemůžeme zde použít await načtiJson(…), protože nejsme uvnitř funkce s async.

Následuje příklad „opětovného vyvolání“. Přepište jej za použití async/await místo .then/catch.

A ve funkci demoUživatelGitHubu se zbavte rekurze ve prospěch cyklu: s async/await to bude lehké.

class ChybaHttp extends Error {
  constructor(odpověď) {
    super(`${odpověď.status} pro ${odpověď.url}`);
    this.name = 'ChybaHttp';
    this.odpověď = odpověď;
  }
}

function načtiJson(url) {
  return fetch(url)
    .then(odpověď => {
      if (odpověď.status == 200) {
        return odpověď.json();
      } else {
        throw new ChybaHttp(odpověď);
      }
    });
}

// Ptáme se na uživatelské jméno, dokud GitHub nevrátí platného uživatele
function demoUživatelGitHubu() {
  let jméno = prompt("Zadejte jméno", "iliakan");

  return načtiJson(`https://api.github.com/users/${jméno}`)
    .then(uživatel => {
      alert(`Celé jméno: ${uživatel.name}.`);
      return uživatel;
    })
    .catch(chyba => {
      if (chyba instanceof ChybaHttp && chyba.odpověď.status == 404) {
        alert("Takový uživatel neexistuje, prosím zadejte znovu.");
        return demoUživatelGitHubu();
      } else {
        throw chyba;
      }
    });
}

demoUživatelGitHubu();

Nejsou tady žádné triky. Stačí uvnitř demoUživatelGitHubu nahradit .catch za try..catch a přidat async/await, kde jsou zapotřebí:

class ChybaHttp extends Error {
  constructor(odpověď) {
    super(`${odpověď.status} pro ${odpověď.url}`);
    this.name = 'ChybaHttp';
    this.odpověď = odpověď;
  }
}

async function načtiJson(url) {
  let odpověď = await fetch(url);
  if (odpověď.status == 200) {
    return odpověď.json();
  } else {
    throw new ChybaHttp(odpověď);
  }
}

// Ptáme se na uživatelské jméno, dokud GitHub nevrátí platného uživatele
async function demoUživatelGitHubu() {

  let uživatel;
  while(true) {
    let jméno = prompt("Zadejte jméno", "iliakan");

    try {
      uživatel = await načtiJson(`https://api.github.com/users/${jméno}`);
      break; // žádná chyba, opustíme cyklus
    } catch(chyba) {
      if (chyba instanceof ChybaHttp && chyba.odpověď.status == 404) {
        // po alertu bude cyklus pokračovat
        alert("Takový uživatel neexistuje, prosím zadejte znovu.");
      } else {
        // neznámá chyba, vyvoláme ji znovu
        throw chyba;
      }
    }
  }


  alert(`Celé jméno: ${uživatel.name}.`);
  return uživatel;
}

demoUživatelGitHubu();

Máme „obyčejnou“ funkci nazvanou f. Jak můžeme volat async funkci čekej() a použít její výsledek uvnitř f?

async function čekej() {
  await new Promise(resolve => setTimeout(resolve, 1000));

  return 10;
}

function f() {
  // ...co byste sem měli napsat?
  // musíme volat asynchronní čekej() a čekat, než obdržíme 10
  // pamatujte, že nemůžeme použít „await“
}

P.S. Tento úkol je technicky velmi jednoduchý, ale tato otázka je u vývojářů, kteří s async/await teprve začínají, vcelku běžná.

Toto je případ, kdy nám pomáhá, že víme, jak to funguje uvnitř.

Jednoduše zacházejte s voláním async jako s příslibem a připojte k němu .then:

async function čekej() {
  await new Promise(resolve => setTimeout(resolve, 1000));

  return 10;
}

function f() {
  // za 1 sekundu zobrazí 10
  čekej().then(výsledek => alert(výsledek));
}

f();

Promise.all je skvělý způsob, jak paralelizovat vícenásobné operace. Obzvláště se hodí, když potřebujeme vytvořit paralelní požadavky na více služeb.

Skrývá se v něm však nebezpečí. V této úloze uvidíme jeho příklad a prozkoumáme, jak se mu vyhnout.

Dejme tomu, že máme připojení ke vzdálené službě, například k databázi.

Máme pro něj dvě funkce: připoj() a odpoj().

Když se připojíme, můžeme posílat požadavky voláním databáze.dotaz(...) – asynchronní funkce, která obvykle vrátí výsledek, ale může také vygenerovat chybu.

Jednoduchá implementace:

let databáze;

function připoj() {
  databáze = {
    async dotaz(jeOk) {
      if (!jeOk) throw new Error('Dotaz selhal');
    }
  };
}

function odpoj() {
  databáze = null;
}

// zamýšlené použití:
// připoj()
// ...
// databáze.dotaz(true) pro emulaci úspěšného volání
// databáze.dotaz(false) pro emulaci neúspěšného volání
// ...
// odpoj()

Nyní zde máme problém.

Napíšeme kód pro připojení a pošleme paralelně 3 dotazy (každý z nich trvá jinou dobu, např. 100, 200 a 300 ms), pak se odpojíme:

// pomocná funkce pro volání asynchronní funkce `fn` za `ms` milisekund
function čekej(fn, ms) {
  return new Promise((splň, zamítni) => {
    setTimeout(() => fn().then(splň, zamítni), ms);
  });
}

async function spusť() {
  připoj();

  try {
    await Promise.all([
      // tyto 3 paralelní úkoly trvají každý jinou dobu: 100, 200 a 300 ms
      // k dosažení tohoto efektu použijeme pomocnou funkci `čekej`
      čekej(() => databáze.dotaz(true), 100),
      čekej(() => databáze.dotaz(false), 200),
      čekej(() => databáze.dotaz(false), 300)
    ]);
  } catch(chyba) {
    console.log('Chyba zpracována (opravdu?)');
  }

  odpoj();
}

spusť();

Dva z těchto dotazů byly neúspěšné, ale my jsme natolik chytří, že jsme volání Promise.all umístili do bloku try..catch.

Ale to nepomůže! Tento skript ve skutečnosti vyvolá nezachycenou chybu v konzoli!

Proč? Jak se tomu vyhnout?

Jádro problému spočívá v tom, že Promise.all se okamžitě zamítne, když bude zamítnut jeden z jeho příslibů, ale neudělá nic, aby zrušilo ostatní přísliby.

V našem případě selže druhý dotaz, takže Promise.all se zamítne a blok try...catch tuto chybu zachytí. Mezitím však ostatní přísliby nejsou ovlivněny – jejich vykonávání nezávisle pokračuje. V našem případě třetí příslib za nějakou dobu vygeneruje chybu sám o sobě. A tato chyba není ničím zachycena a my ji uvidíme v konzoli.

Tento problém je obzvláště nebezpečný v prostředích na straně serveru, například Node.js, kde nezachycená chyba může vést k havárii procesu.

Jak to opravit?

Ideální řešení by bylo zrušit všechny nedokončené dotazy ve chvíli, kdy jeden z nich selže. Tímto způsobem se vyhneme všem potenciálním chybám.

Špatná zpráva však je, že volání služeb (např. databáze.dotaz) je často implementováno knihovnou třetí strany, která nepodporuje rušení. Pak neexistuje žádný způsob, jak volání zrušit.

Jako alternativu si můžeme napsat vlastní obalovou funkci kolem Promise.all, která přidá ke každému příslibu vlastní handler then/catch, který je bude sledovat: výsledky se shromáždí, a pokud dojde k chybě, všechny ostatní přísliby jsou ignorovány.

function vlastníPromiseAll(přísliby) {
  return new Promise((splň, zamítni) => {
    const výsledky = [];
    let početVýsledků = 0;
    let mámeChybu = false; // po první chybě to nastavíme na true

    přísliby.forEach((příslib, index) => {
      příslib
        .then(výsledek => {
          if (mámeChybu) return; // pokud už máme chybu, příslib ignorujeme
          výsledky[index] = výsledek;
          početVýsledků++;
          if (početVýsledků === přísliby.length) {
            splň(výsledky); // když jsou všechny výsledky připraveny - úspěch
          }
        })
        .catch(chyba => {
          if (mámeChybu) return; // pokud už máme chybu, příslib ignorujeme
          mámeChybu = true; // ouha, chyba!
          zamítni(chyba); // zamítnutí při selhání
        });
    });
  });
}

Tento přístup má sám o sobě problém – často není žádoucí volat odpoj(), když jsou dotazy ještě zpracovávány.

Může být důležité, aby se všechny dotazy zpracovaly, zvláště když některé z nich provádějí důležité zápisy do databáze.

Než tedy budeme pokračovat v provádění a nakonec se odpojíme, měli bychom počkat, než budou všechny přísliby usazeny.

Zde je jiná implementace. Chová se podobně jako Promise.all – také se vyhodnotí při první chybě, ale počká, dokud nebudou všechny přísliby usazeny.

function vlastníPromiseAllSČekáním(přísliby) {
  return new Promise((splň, zamítni) => {
    const výsledky = new Array(přísliby.length);
    let početUsazených = 0;
    let prvníChyba = null;

    přísliby.forEach((příslib, index) => {
      Promise.splň(příslib)
        .then(výsledek => {
          výsledky[index] = výsledek;
        })
        .catch(chyba => {
          if (prvníChyba === null) {
            prvníChyba = chyba;
          }
        })
        .finally(() => {
          početUsazených++;
          if (početUsazených === přísliby.length) {
            if (prvníChyba !== null) {
              zamítni(prvníChyba);
            } else {
              splň(výsledky);
            }
          }
        });
    });
  });
}

Nyní await vlastníPromiseAllSČekáním(...) pozastaví provádění, dokud nebudou všechny dotazy zpracovány.

Tento přístup je spolehlivější, neboť zaručuje předvídatelný průběh provádění.

Nakonec, kdybychom chtěli zpracovat všechny chyby, můžeme buď použít Promise.allSettled, nebo kolem něj napsat obal, který shromáždí všechny chyby do jediného objektu AggregateError a zamítne se s ním.

// počkáme na usazení všech příslibů
// pokud nebyla žádná chyba, vrátíme výsledky
// pokud byly chyby, vygenerujeme AggregateError se všemi chybami
function všechnoNeboAggregateError(přísliby) {
  return Promise.allSettled(přísliby).then(výsledky => {
    const chyby = [];
    const hodnoty = [];

    výsledky.forEach((výsl, i) => {
      if (výsl.status === 'fulfilled') {
        hodnoty[i] = výsl.value;
      } else {
        chyby.push(výsl.reason);
      }
    });

    if (chyby.length > 0) {
      throw new AggregateError(chyby, 'Jeden nebo více příslibů selhalo');
    }

    return hodnoty;
  });
}
Mapa tutoriálu

Komentáře

přečtěte si před komentováním…
  • Máte-li návrhy na zlepšení, vytvořte prosím issue na GitHubu nebo pull request místo komentáře.
  • Pokud v článku něčemu nerozumíte, napište prosím, čemu přesně a na kterém místě.
  • Pro vložení několika slov kódu použijte značku <code>, pro několik řádků je obalte značkou <pre>, pro více než 10 řádků vložte odkaz na pískoviště (plnkr, jsbin, codepen…)