Přeskočit na obsah

Druhá deklarace const v try-bloku (TDZ) maskuje validační chyby jako 500

import { Aside } from ‘@astrojs/starlight/components’;

Registrační (nebo jiný validující) endpoint vrací „Internal server error” (500) pokaždé, když uživatel pošle formulář s JAKOUKOLIV validační chybou (chybí pole, slabé heslo…). Místo srozumitelné hlášky „Vyplňte bankovní účet” dostane uživatel generickou 500. Plně validní data ale projdou. V logu: Cannot access 'lang2' before initialization (bundler přejmenoval stíněnou proměnnou).

Ve funkci je proměnná deklarovaná u vstupu a používaná napříč try blokem, a NÍŽE v témže try bloku je deklarovaná podruhé pod stejným názvem:

const lang = pickLangFromHeader(req); // (1) function scope
try {
if (!valid(email)) errors.push(tApi('reg.invalid_email', lang)); // (2) ⚠ TDZ
// ... desítky dalších tApi(..., lang)
const lang = body.language || 'cs'; // (3) hoistne se na VRCHOL try-bloku
sendEmail(tApi('welcome', lang));
} catch (e) {
return json500(tApi('err.internal', lang)); // (4) tady už outer lang — OK
}

const na řádku (3) je block-scoped a hoistne se na začátek try bloku. Tím každá reference lang mezi vrcholem bloku a řádkem (3) — tj. všechny validační tApi(..., lang) — spadne do Temporal Dead Zone a vyhodí ReferenceError: Cannot access 'lang' before initialization. Výjimku chytí catch, kde už lang odkazuje na vnější (definovanou) proměnnou, takže se tiše vrátí „Internal server error”. Happy-path bez validačních chyb se k žádné dřívější referenci nedostane → projde, takže bug vypadá náhodně.

Přejmenuj vnitřní proměnnou (nestínit):

const lang = pickLangFromHeader(req);
try {
if (!valid(email)) errors.push(tApi('reg.invalid_email', lang)); // ✓ outer lang
const emailLang = body.language || 'cs'; // ✓ jiný název, žádná TDZ
sendEmail(tApi('welcome', emailLang));
} catch (e) { /* … */ }

Po opravě validační chyba vrátí korektní 400 s lokalizovanou hláškou, ne 500.

Jak se tomu vyvarovat v jiných systémech

Sekce “Jak se tomu vyvarovat v jiných systémech”
  • Detection: grep duplicitní deklarace v jedné funkci: grep -nE "const (lang|id|user|data) =" soubor a hledej dva výskyty téhož jména. ESLint no-shadow + no-redeclare to chytí staticky. Pozor: TypeScript to NEohlásí jako chybu (je to validní JS s block-scope), takže tsc/build projde čistě.
  • Anti-pattern: dlouhá funkce s try blokem, kde se „pomocná” proměnná deklaruje až v půlce, ačkoli se stejné jméno už používá výš.
  • Lepší přístup: krátké funkce; deklaruj všechny proměnné nahoře; catch-all catch nikdy nesmí vracet generickou 500 bez zalogování skutečné výjimky (jinak ti TDZ/typové chyby splynou s „normální” 500 a nikdo si jich nevšimne).

Sister bugs / související

Sekce “Sister bugs / související”

Generický catch → 500 je tady spoluviník — kdyby endpoint logoval err.message viditelně (ne jen do tabulky), Cannot access 'lang' before initialization by se odhalilo hned. Příbuzné: jakákoli „náhodná 500 jen někdy” často = výjimka na konkrétní větvi spolknutá do generické odpovědi.

Přidal aiarchitekt.cz · 24. 6. 2026 2:00
Provozuje aiarchitekt.cz