Express alapok · 1. tananyag
Az 1. blokkban megtanultuk, hogy a Node.js azért bír el sok felhasználót, mert nem várakozik. Ez a fejezet arról szól, hogyan néz ki ez a kódban. Enélkül nem tudsz továbbmenni: az Express minden példája, a Prisma minden lekérdezése és a fájlkezelés is aszinkron.
.then() és a .catch();async/await
kulcsszavakkal, olvashatóan;await-et.A JavaScript alapból szinkron: a sorok egymás után futnak le, és amíg egy sor be nem fejeződik, a következő nem indul el.
console.log("első"); console.log("második"); console.log("harmadik");
Kimenet — pontosan a várt sorrendben:
első
második
harmadik
Ez addig kényelmes, amíg minden művelet gyors. De mi van, ha egy sor sokáig tart?
Képzeld el, hogy a második sor egy adatbázis-lekérdezés, ami 200 ezredmásodpercig fut. Szinkron világban ez azt jelentené, hogy a program áll 200 ezredmásodpercig — és mivel a Node egy szálon futtatja a kódodat, ez alatt egyetlen másik kérést sem tudna kiszolgálni.
A blokkoló pincér bemegy a konyhába, és ott áll, amíg elkészül az étel. A Node.js pincére leadja a rendelést, és továbbmegy a következő asztalhoz. Ez a fejezet arról szól, hogyan írjuk le ezt kódban.
Az aszinkron művelet elindul, a program pedig továbbmegy. Amikor a művelet befejeződik, a Node visszatér hozzá.
console.log("első"); setTimeout(() => { console.log("második — 2 másodperc múlva"); }, 2000); console.log("harmadik");
Kimenet:
első
harmadik
második — 2 másodperc múlva
A „harmadik” hamarabb jelenik meg, mint a „második”. Ez nem hiba: a
setTimeout csak beütemezte a feladatot, majd a program azonnal
továbbment.
Az aszinkron nem azt jelenti, hogy „egyszerre fut több dolog”. A te kódod továbbra is egy szálon fut, egyszerre egy sor. Csak a várakozás kerül a háttérbe.
| Réteg | Mit csinál? |
|---|---|
| A te kódod | Egy szálon fut, sorról sorra |
| A Node beépített részei | Ezek végzik a lassú műveletet: fájlolvasás, hálózat, időzítő |
| Az eseményhurok | Amikor a művelet kész, visszateszi az eredményt a te kódod sorába |
Amikor tehát „vársz” egy adatbázisra, nem a te szálad áll — hanem a Node egy másik része dolgozik, a te szálad pedig közben más kéréseket vesz fel.
Nem kell mélyen érteni, de a fogalmakat ismerd fel, mert a hibaüzenetekben előkerülnek.
| Fogalom | Mi ez? |
|---|---|
| Hívási verem (call stack) | A függvényhívások egymásra pakolt listája. A hibaüzenet „stack trace” része pontosan ezt írja ki |
| Feladatsor (queue) | Az elkészült aszinkron műveletek várakozó eredményei |
| Eseményhurok (event loop) | Amikor a verem kiürül, kivesz egy elemet a sorból, és lefuttatja |
Ha a te kódod hosszan számol (például egy milliós ciklus), az eseményhurok nem tud dolgozni, tehát a szerver közben senkit nem szolgál ki. Az aszinkronitás a várakozásra való, nem a számolásra.
A Promise előtt az aszinkron eredményt egy átadott függvénnyel kaptuk vissza. Ezt hívjuk callbacknek (visszahívás):
import fs from "node:fs"; fs.readFile("adatok.txt", "utf8", (hiba, tartalom) => { if (hiba) { console.error("Nem sikerült:", hiba.message); return; } console.log(tartalom); }); console.log("ez hamarabb fut le!");
A harmadik paraméter egy függvény: a Node majd akkor hívja meg, amikor a fájl beolvasása kész.
A Node-ban a callback első paramétere mindig a hiba — ha nincs hiba, akkor
null. Ezt hívjuk error-first callback mintának, és minden
régebbi Node-könyvtárban így néz ki.
Mert ha több művelet épül egymásra, a kód jobbra csúszik és olvashatatlanná válik:
olvasFajlt("a.txt", (h1, a) => { if (h1) return kezel(h1); olvasFajlt("b.txt", (h2, b) => { if (h2) return kezel(h2); olvasFajlt("c.txt", (h3, c) => { if (h3) return kezel(h3); console.log(a + b + c); // ← itt vagyunk, négy szinttel beljebb }); }); });
Ezt hívják callback pokolnak (callback hell). Figyeld meg: a hibakezelés is háromszor szerepel benne.
Mert régebbi kódokban és néhány könyvtárban ma is találkozol vele — és mert így érted meg, mi elől menekülünk a Promise-szal.
A Promise egy objektum, amely egy később megérkező értéket képvisel. Nem maga az érték, hanem az ígéret, hogy lesz érték (vagy hiba).
.finally() mindkét ág után lefut..then() és .catch()import fs from "node:fs/promises"; // figyelj: a /promises változat! fs.readFile("adatok.txt", "utf8") .then((tartalom) => { console.log(tartalom); }) .catch((hiba) => { console.error("Nem sikerült:", hiba.message); }) .finally(() => { console.log("ez mindenképp lefut"); });
| Metódus | Mikor fut le? |
|---|---|
.then(fv) | Ha a Promise teljesült — a függvény megkapja az eredményt |
.catch(fv) | Ha elutasított — a függvény megkapja a hibát |
.finally(fv) | Mindkét esetben, a végén |
A .then() visszatérési értéke újabb Promise, ezért fűzhető
tovább — így a beljebb csúszás megszűnik:
olvasFajlt("a.txt") .then((a) => olvasFajlt("b.txt")) .then((b) => olvasFajlt("c.txt")) .then((c) => console.log("kész")) .catch((hiba) => console.error(hiba.message)); // EGY hibakezelő az egész láncra
Ez már sokkal jobb — de van ennél is olvashatóbb megoldás.
async / await — a mindennapi eszközödAz async és az await kulcsszó nem új
működés, csak egy olvashatóbb írásmód ugyanarra a Promise-ra.
import fs from "node:fs/promises"; async function beolvas() { const tartalom = await fs.readFile("adatok.txt", "utf8"); console.log(tartalom); } beolvas();
| Kulcsszó | Hova kerül? | Mit csinál? |
|---|---|---|
async |
A függvény elé | Megjelöli, hogy a függvény aszinkron. Ettől a függvény mindig Promise-t ad vissza,
és belül használható az await |
await |
Egy Promise elé | Megvárja az eredményt, és „kicsomagolja” — a változóba már a kész érték kerül |
Nézd meg egymás mellett — mindhárom ugyanazt csinálja:
olvasFajlt("a.txt", (hiba, tart) => { if (hiba) { return console.error(hiba); } console.log(tart); });
olvasFajlt("a.txt") .then((tart) => { console.log(tart); }) .catch((hiba) => { console.error(hiba); });
try { const tart = await olvasFajlt("a.txt"); console.log(tart); } catch (hiba) { console.error(hiba); }
A harmadik változat majdnem úgy néz ki, mint a hagyományos kód — pedig nem blokkol.
await NEM CSINÁL
Nem állítja meg a szervert! Csak ezt a függvényt függeszti fel, amíg az eredmény megérkezik. A Node közben nyugodtan kiszolgál másik kérést.
await?async függvényen belül — bárhol;"type": "module" beállítással a fájlod modul. Ez működik, de a
függvényen belüli használat a gyakoribb.// index.js — ESM-modulban ez is működik const adat = await olvasFajlt("config.json");
Öt kódrészlet. Olvasd el, döntsd el a kimenet sorrendjét — aztán nézd meg, miért. Ez a leggyakoribb félreértés az aszinkron kód körül.
Ez a rész legalább olyan fontos, mint maga az await.
async function termekLekeres(id) { try { const valasz = await fetch(`https://api.pelda.hu/products/${id}`); if (!valasz.ok) { // a fetch NEM dob hibát 404-nél! throw new Error(`A szerver ${valasz.status} kóddal válaszolt`); } const termek = await valasz.json(); return termek; } catch (hiba) { console.error("Nem sikerült lekérni a terméket:", hiba.message); throw hiba; // továbbadjuk a hívónak } }
| Sor | Mit csinál? |
|---|---|
try { … } | Ide kerül minden, ami elromolhat |
await fetch(…) | Megvárja a hálózati választ |
if (!valasz.ok) | Fontos: a fetch csak akkor dob kivételt, ha a kérés el sem jut a szerverig. A 404 és az 500 „sikeres kérésnek” számít — a státuszkódot nekünk kell megnéznünk |
throw new Error(…) | Saját hibát dobunk, hogy a catch elkapja |
catch (hiba) | Itt kezeljük — naplózunk, majd továbbdobjuk |
fetch KÖRÜL
Sokan azt hiszik, hogy a fetch hibát dob 404-nél. Nem dob.
Ellenőrizve: a válasz status: 404, ok: false —
és a catch ág meg sem hívódik.
A fetch csak akkor dob, ha a kérés el sem jutott a szerverig:
nincs hálózat, nem oldható fel a név, megszakadt a kapcsolat. A státuszkódot mindig neked
kell megnézned a valasz.ok vagy a
valasz.status tulajdonsággal.
Ha nincs try/catch, és a Promise elutasításra
kerül, a hiba kezeletlen marad (unhandled rejection). Node-ban ez a folyamat
leállásához vezethet — vagyis a szervered egyszer csak eltűnik.
Az Express 5-ben a keretrendszer elkapja helyettünk az útvonalakban keletkező aszinkron hibákat — de erről majd a 8. fejezetben lesz szó.
Ez az a rész, ahol az await könnyen lassú kódot eredményez.
const a = await lekeres("A"); // 1 mp const b = await lekeres("B"); // + 1 mp const c = await lekeres("C"); // + 1 mp // összesen kb. 3 másodperc
Ez akkor helyes, ha b-hez kell a
eredménye. De ha a három művelet független egymástól, feleslegesen várunk.
Promise.allconst [a, b, c] = await Promise.all([ lekeres("A"), lekeres("B"), lekeres("C"), ]); // összesen kb. 1 másodperc — a leglassabbé
A Promise.all az első hibánál elutasításra kerül: ha egy
művelet elhasal, az egész elhasal. Ha mindegyik eredményére szükséged van — sikertől függetlenül —,
akkor a Promise.allSettled a jó választás.
| Metódus | Mit ad vissza? | Mikor használd? |
|---|---|---|
Promise.all([…]) | Az összes eredmény tömbben — vagy az első hiba | Minden művelet kell, és mindnek sikerülnie kell |
Promise.allSettled([…]) | Minden elemről egy { status, value / reason } objektum | Akkor is kell az eredmény, ha némelyik elhasal |
Promise.race([…]) | A leggyorsabb eredménye | Időkorlát megvalósítása |
Hat helyzet. Döntsd el mindegyikről, hogy a műveleteknek egymás után kell-e futniuk, vagy indíthatók egyszerre! A visszajelzés megmondja, mennyi időt nyersz vagy veszítesz.
awaitasync function rosszul() { const termek = lekeres(42); // ❌ hiányzik az await console.log(termek.nev); // TypeError: Cannot read properties of undefined }
A termek változóban nem a termék van, hanem maga a Promise.
Ha kiíratod:
Promise { <pending> }
Ha a console.log kimenetében megjelenik a
Promise szó, ott hiányzik egy await.
Pontosítás: a <pending> felirat csak akkor jelenik
meg, ha a művelet tényleg fut még. Ha az async függvény azonnal visszatér,
a kiírás Promise { 42 } alakú lesz — vagyis a
Promise szó a gyanús, nem a <pending>.
await nem async függvénybenfunction rosszul() { const adat = await lekeres(); // ❌ SyntaxError }
A hibaüzenet attól függ, milyen modulrendszert használsz — és pont a mi esetünkben a kevésbé beszédes:
| Modulrendszer | A hibaüzenet |
|---|---|
CommonJS (require) |
SyntaxError: await is only valid in async functions and the top level bodies of modules |
ESM ("type": "module" — mi ezt használjuk) |
SyntaxError: Unexpected reserved word |
Az Unexpected reserved word („váratlan foglalt szó”) önmagában semmit nem
mond. Ha ezt látod, és a jelölt sorban await szerepel, akkor szinte
biztosan az async hiányzik a függvény elől.
Ellenőrizve Node.js 24-en:
"type": "module" esetén Unexpected reserved word,
.cjs fájlnál a hosszabb, beszédesebb üzenet.
asyncasync function osszead(a, b) { // ❌ semmi aszinkron nincs benne return a + b; }
Ez működik, de félrevezető: aki látja, azt hiszi, van benne várakozás — ráadásul a visszatérési érték
Promise lesz, tehát a hívónak await-elnie kell.
Csak akkor tedd ki, ha tényleg van benne await.
await ciklusban, feleslegesenfor (const id of azonositok) { const t = await lekeres(id); eredmenyek.push(t); }
const eredmenyek = await Promise.all( azonositok.map((id) => lekeres(id)) );
Ha 10 000 elemre indítanál egyszerre kérést, azzal megterhelnéd a szervert. Néhány tucat elemnél a
Promise.all a jó választás; ennél többnél kötegelni kell.
| Feladat | Hogyan néz ki? |
|---|---|
| Fájl olvasása | const t = await fs.readFile("a.txt", "utf8"); |
| HTTP-kérés | const v = await fetch(url); const adat = await v.json(); |
| Várakozás | await new Promise((r) => setTimeout(r, 1000)); |
| Adatbázis (3. blokk, Prisma) | const termekek = await prisma.product.findMany(); |
| Express útvonal (4. fejezet) | app.get("/api/products", async (req, res) => { … }); |
A fetch a Node 21 óta beépített — nem kell hozzá csomagot
telepíteni.
await a fetch-nél?const valasz = await fetch(url); // 1) megjött a VÁLASZ FEJRÉSZE const adat = await valasz.json(); // 2) beolvastuk és JSON-ná alakítottuk a TÖRZSET
A válasz fejlécei hamarabb megérkeznek, mint a teljes törzs — ezért két lépés. Ez elsőre furcsa, de logikus: a státuszkódot már azelőtt megnézheted, hogy letöltenéd a tartalmat.
Ott láttuk, hogy a HTTP-válasz kezdősorból, fejlécekből és törzsből áll. A
fetch két lépése pontosan ezt a szerkezetet követi: előbb a kezdősor és a
fejlécek, aztán a törzs.
Sorrend. Mit ír ki ez a program, és milyen sorrendben? Indokold!
console.log("A"); setTimeout(() => console.log("B"), 0); console.log("C");
Javítás. Mi a hiba, és hogyan javítod? Milyen hibaüzenetet kapsz ESM-ben?
function adatLekeres() { const adat = await fetch("https://pelda.hu/api"); return adat; }
Mit ír ki? Miért, és hogyan javítanád?
async function fo() { const t = lekeresTermek(1); console.log(t); }
Sorosan vagy párhuzamosan? Döntsd el mindegyikről, és indokold:
Átírás. Írd át async/await
szintaxisra, hibakezeléssel együtt:
lekeres("/api/users/1") .then((u) => lekeres(`/api/orders?userId=${u.id}`)) .then((r) => console.log(r.length)) .catch((e) => console.error(e.message));
Késleltetés. Írj egy varj(ms) függvényt, amely a megadott
ideig vár, majd folytatja. Használd egy ciklusban: írja ki a számokat 1-től 5-ig, másodpercenként
egyet.
function varj(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); }
Nyilvános API. Kérd le a
https://jsonplaceholder.typicode.com/users/1 címről a felhasználót
fetch-csel, és írd ki a nevét és az e-mail-címét. Kezeld a hibát
try/catch-csel!
Hibás cím. Az előző feladatban írd át a címet egy nem létező végpontra
(/users/9999). Mit tapasztalsz? Miért nem ugrik a
catch ágba? Egészítsd ki a kódot úgy, hogy a nem sikeres státuszkódot is
hibaként kezelje!
Párhuzamosítás mérése. Kérd le három felhasználó adatait (1, 2, 3 azonosítóval).
Írd meg kétféleképpen: egymás után await-tel, illetve
Promise.all-lal. Mérd meg mindkettő futásidejét
(console.time / console.timeEnd), és
hasonlítsd össze az eredményt!
Tíz kérdés. Válassz, és azonnal megtudod, jó volt-e — magyarázattal.
pending, fulfilled,
rejected. Az állapot egyszer változik meg.async/await ugyanaz a
Promise, olvashatóbb írásmóddal — ezt használjuk mindenhol.try/catch blokkal
kezelünk. A fetch nem dob hibát 404 vagy 500 esetén —
a státuszkódot nekünk kell megnéznünk.Promise.all-lal — ez sokszoros gyorsulást jelenthet.await.
Ismertetőjele: a Promise szó a kimenetben, vagy
undefined egy mező helyén.| Fogalom | Jelentés |
|---|---|
| Szinkron | A sorok egymás után futnak; a következő megvárja az előzőt |
| Aszinkron | A művelet elindul, a program továbbmegy; az eredmény később érkezik |
| Callback | Átadott függvény, amit a művelet befejeztével hívnak meg |
| Error-first callback | Node-os minta: a callback első paramétere a hiba |
| Callback hell | Egymásba ágyazott callbackek, jobbra csúszó, olvashatatlan kód |
| Promise | Objektum, amely egy később megérkező értéket képvisel |
pending / fulfilled / rejected | A Promise három állapota: függőben / teljesült / elutasított |
async | Megjelöli a függvényt: Promise-t ad vissza, és await használható benne |
await | Megvárja a Promise eredményét, és kicsomagolja |
| Hívási verem (call stack) | Az egymásba ágyazott függvényhívások listája — ezt írja ki a stack trace |
| Eseményhurok (event loop) | Kész eredményeket vesz elő, ha a verem üres |
Promise.all | Több művelet párhuzamosan; az első hibánál elhasal |
Promise.allSettled | Ugyanaz, de minden elemről megmondja, sikerült-e |
| Unhandled rejection | Kezeletlen Promise-hiba — le is állíthatja a folyamatot |
| Top-level await | await a modul legfelső szintjén, függvényen kívül — ESM-ben működik |