Async, Promises i obsługa błędów

Frontend ZEUS nieustannie rozmawia z API. Asynchroniczny kod, który źle obsługuje błędy, daje białe ekrany i ciche awarie. Ta lekcja porządkuje wzorce async/await i obsługę błędów.

async/await zamiast łańcuchów .then

// czytelnie — async/await
async function loadProbes(): Promise<Probe[]> {
  const res = await fetch("/api/probes");
  if (!res.ok) {
    throw new Error(`API ${res.status}`);
  }
  return res.json();
}

Standard ProfessNet: fetch nie rzuca wyjątku dla statusów 4xx/5xx. Zawsze sprawdzamy res.ok i rzucamy jawny błąd — inaczej res.json() zwróci treść błędu jako „dane".

Równoległość

Niezależne żądania uruchamiamy współbieżnie, nie jedno po drugim.

// wolno — sekwencyjnie
const probes = await loadProbes();
const alerts = await loadAlerts();

// szybko — równolegle
const [probes, alerts] = await Promise.all([loadProbes(), loadAlerts()]);

Gdy część może się nie udać, a reszta ma działać dalej, używamy allSettled:

const results = await Promise.allSettled(forests.map(scanForest));
const ok = results.filter((r) => r.status === "fulfilled");

Obsługa błędów

Każde await z I/O ma swój try/catch lub jest opakowane wyżej.

async function refresh() {
  try {
    const data = await loadProbes();
    setProbes(data);
  } catch (err) {
    console.error("Nie udało się pobrać probe'ów", err);
    toast.error("Błąd pobierania danych");
  }
}

Standard ProfessNet: nie połykamy błędów po cichu. Co najmniej log + sygnał dla użytkownika (toast/komunikat). Pusty catch {} jest zabroniony.

error jest typu unknown

W TypeScript strict błąd w catch ma typ unknown — trzeba go zawęzić.

catch (err) {
  const msg = err instanceof Error ? err.message : "Nieznany błąd";
  toast.error(msg);
}

Timeout i anulowanie

Żądania mogą wisieć. Używamy AbortController do timeoutu i sprzątania.

async function loadWithTimeout(url: string, ms = 10_000) {
  const ctrl = new AbortController();
  const t = setTimeout(() => ctrl.abort(), ms);
  try {
    const res = await fetch(url, { signal: ctrl.signal });
    return await res.json();
  } finally {
    clearTimeout(t);
  }
}

W useEffect AbortController chroni przed aktualizacją odmontowanego komponentu:

useEffect(() => {
  const ctrl = new AbortController();
  fetch("/api/probes", { signal: ctrl.signal })
    .then((r) => r.json())
    .then(setProbes)
    .catch(() => {});
  return () => ctrl.abort();
}, []);
WzorzecDo czego
await + res.okpojedyncze żądanie z kontrolą statusu
Promise.allwiele żądań, wszystkie muszą się udać
Promise.allSettledwiele żądań, część może paść
AbortControllertimeout i sprzątanie w useEffect

Wskazówka: w komponentach klienckich rozważ bibliotekę typu TanStack Query — daje cache, retry, stany ładowania i anulowanie out-of-the-box.


Sprawdzaj res.ok, łap błędy, pokazuj je użytkownikowi i anuluj wiszące żądania. To różnica między aplikacją, która „czasem nie działa", a taką, której się ufa.