Promise i async/await — wynik przyjdzie później

Program nie musi zatrzymywać całej pracy podczas oczekiwania. Przećwicz sukces, błąd i kilka równoległych operacji bez połączenia z internetem.

CEL LEKCJI

Po tej lekcji potrafisz

  • Odczytasz Promise<T> przez await i then.
  • Obsłużysz odrzucenie Promise w catch.
  • Dobierzesz wykonanie sekwencyjne lub Promise.all.
WPROWADZENIE

Wczytywanie wyników turnieju

W prawdziwej aplikacji dane mogą przyjść z pliku albo serwera. W ćwiczeniach użyjemy lokalnych obietnic i krótkiego timera, żeby każdy uczeń dostał ten sam wynik bez sieci. Promise reprezentuje przyszły rezultat, a nie samą gotową listę.

POZNAJ SKŁADNIĘ

Małe kroki, konkretne efekty

async zawsze zwraca Promise

Wartość zwrócona przez async zostaje opakowana w obietnicę. await w funkcji async wstrzymuje dalszą część tej funkcji do rozstrzygnięcia obietnicy. Nie blokuje całego programu.

src/index.ts — osobny przykład
async function punkty(): Promise<number> { return 42; }
async function main(): Promise<void> {
  const wynik = await punkty();
  console.log("Punkty: " + wynik);
}
main().catch(console.error);
wynik w konsoli
Punkty: 42

main().catch(…) obsługuje odrzucenie głównej funkcji. Samo void main() nie przechwytuje błędów.

then działa po bieżącym kodzie

Obietnica może być już spełniona, ale callback then i tak wykona się po zakończeniu bieżącego synchronicznego fragmentu. Nie próbuj odczytywać przyszłego wyniku z zewnętrznej zmiennej zaraz po wywołaniu.

src/index.ts — osobny przykład
console.log("A");
Promise.resolve(7).then(n => console.log("B: " + n));
console.log("C");
wynik w konsoli
A
C
B: 7

Promise ma stan oczekiwania, spełnienia lub odrzucenia. Wynik rozstrzygniętej obietnicy nie zmienia się ponownie.

PRZYKŁAD Z OMÓWIENIEM

Wyniki z symulowanego źródła

Timer symuluje krótkie oczekiwanie. Wariant false zgłasza problem połączenia. Nie pobieramy żadnych danych z internetu.

src/index.ts — osobny przykład
function pobierz(dostepne: boolean): Promise<number[]> {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (dostepne) { resolve([10, 20, 30]); }
      else { reject(new Error("Źródło niedostępne")); }
    }, 10);
  });
}
async function raport(ok: boolean): Promise<void> {
  try {
    const wyniki = await pobierz(ok);
    console.log("Suma: " + wyniki.reduce((s, n) => s + n, 0));
  } catch (e) {
    console.log(e instanceof Error ? e.message : "Nieznany błąd");
  }
}
async function main(): Promise<void> {
  await raport(true);
  await raport(false);
}
main().catch(console.error);
wynik w konsoli
Suma: 60
Źródło niedostępne

Jak program dochodzi do wyniku

  1. new Promise otrzymuje funkcję z resolve i reject. To narzędzia do rozstrzygnięcia obietnicy.
  2. setTimeout planuje callback; 10 ms to próg, nie gwarantowany dokładny czas wykonania.
  3. await sprawia, że odrzucenie trafia do otaczającego catch.
  4. Dwa await w main gwarantują kolejność raportów.

Zanim przejdziesz dalej

Czy try { pobierz(false); } catch (…) przejmie późniejsze odrzucenie?

Sprawdź swoje rozumowanie

Nie. Trzeba użyć await wewnątrz try albo dołączyć catch do zwróconej obietnicy.

EKSPERYMENT

Zmień kod i zobacz, co się stanie

Kilka niezależnych wyników

Promise.all zbiera wyniki w kolejności wejściowych obietnic, nie kolejności ich zakończenia. Odrzuci się po odrzuceniu jednej; pozostałych operacji automatycznie nie anuluje.

src/index.ts — osobny przykład
async function main(): Promise<void> {
  const [a, b] = await Promise.all([Promise.resolve(10), Promise.resolve(20)]);
  console.log(a + b);
  const wyniki = await Promise.allSettled([Promise.resolve("OK"), Promise.reject(new Error("Brak"))]);
  for (const w of wyniki) { console.log(w.status); }
}
main().catch(console.error);
wynik w konsoli
30
fulfilled
rejected

allSettled czeka na wszystkie wyniki i opisuje każdy statusem. forEach z async nie czeka na callbacki. Do kolejności użyj for…of z await, a do niezależnych operacji map i Promise.all.

CZĘSTE BŁĘDY

Gdy wynik zaskakuje

Pomyłka Co się dzieje i jak to poprawić
Promise potraktowane jak gotowe dane Najpierw await lub then.
Brak await wewnątrz try Późniejsze odrzucenie nie zostanie przejęte przez taki catch.
Asynchroniczność utożsamiona z osobnym wątkiem Promise i async same nie przenoszą obliczeń do innego wątku. Długa synchroniczna pętla nadal blokuje.
ZADANIA

Teraz Twoja kolej

Rozwiązanie jest jedną z możliwości. Zanim je odkryjesz, uruchom własną wersję dla podanych danych i przynajmniej jednego przypadku brzegowego.

ZAD 1Obietnica nagrody★☆☆

Funkcja async nagroda zwraca 100. Odczytaj wynik w async main i wypisz go.

Mała podpowiedź

Typ wyniku nagrody to Promise<number>.

Przykładowe rozwiązanie z wyjaśnieniem
src/index.ts — osobny przykład
async function nagroda(): Promise<number> { return 100; }
async function main(): Promise<void> { console.log(await nagroda()); }
main().catch(console.error);
wynik w konsoli
100

await daje liczbę znajdującą się w spełnionej obietnicy.

ZAD 2Kolejność komunikatów★★☆

Zapisz i sprawdź kolejność: „Start”, callback then z „Wynik”, „Dalej”.

Mała podpowiedź

then nie wykona callbacku w środku bieżącego fragmentu.

Przykładowe rozwiązanie z wyjaśnieniem
src/index.ts — osobny przykład
console.log("Start");
Promise.resolve().then(() => console.log("Wynik"));
console.log("Dalej");
wynik w konsoli
Start
Dalej
Wynik

To podstawowe ćwiczenie rozumienia kolejności, bez zależności od długości timerów.

ZAD 3Obsłuż odmowę★★☆

Funkcja async ma rzucić Error(„Serwer zajęty”). Odbiorca powinien wypisać ten komunikat przez catch.

Mała podpowiedź

throw wewnątrz async odrzuca zwróconą obietnicę.

Przykładowe rozwiązanie z wyjaśnieniem
src/index.ts — osobny przykład
async function pobierz(): Promise<number> { throw new Error("Serwer zajęty"); }
async function main(): Promise<void> {
  try { console.log(await pobierz()); }
  catch (e) { if (e instanceof Error) { console.log(e.message); } }
}
main().catch(console.error);
wynik w konsoli
Serwer zajęty

Obsługujemy błąd w miejscu, które wie, jak go pokazać.

ZAD 4Trzy profile naraz★★★

Dla id [1, 2, 3] wywołaj async nick zwracającą „Gracz ” + id. Zbierz wyniki przez map i Promise.all, wypisz w jednej linii.

Mała podpowiedź

map tworzy tablicę Promise<string>.

Przykładowe rozwiązanie z wyjaśnieniem
src/index.ts — osobny przykład
async function nick(id: number): Promise<string> { return "Gracz " + id; }
async function main(): Promise<void> {
  const nicki = await Promise.all([1, 2, 3].map(id => nick(id)));
  console.log(nicki.join(", "));
}
main().catch(console.error);
wynik w konsoli
Gracz 1, Gracz 2, Gracz 3

Wynik zachowuje kolejność id. all nie oznacza automatycznie osobnych wątków.

PODSUMOWANIE

Co zabierasz do następnej lekcji

  • Promise<T> reprezentuje przyszły wynik typu T.
  • await ułatwia zapis kolejności i obsługi odrzuceń.
  • Promise.all i allSettled służą różnym oczekiwaniom wobec błędów.