Vezérlőpult
1. Aszinkron JavaScript · Express alapok

Express alapok · 1. tananyag

Aszinkron JavaScript

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.

3 tanóraAz egész blokk előfeltételePromise · async/await

A fejezet végén tudni fogod

1Szinkron kód: egyszerre egy dolog

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.

EMLÉKEZTETŐ AZ 1. BLOKKBÓL

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.

2Aszinkron kód: „szólj, ha kész”

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.

FONTOS — az aszinkron nem párhuzamos

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.

SZINKRON — a várakozás foglalja a szálat kód várakozás: adatbázis várakozás: fájl A szaggatott szakaszokon a program áll — más kérést sem tud kiszolgálni. ASZINKRON — a várakozás a háttérbe kerül kód háttérben: adatbázis kód háttérben: fájl Ugyanaz a munka, töredék idő alatt — és közben más kérés is sorra kerülhet. idő
1. ábra — Ugyanaz a két művelet. A különbség nem a sebesség, hanem hogy mi történik a várakozás alatt.

Ki csinálja a háttérben?

RétegMit csinál?
A te kódodEgy szálon fut, sorról sorra
A Node beépített részeiEzek végzik a lassú műveletet: fájlolvasás, hálózat, időzítő
Az eseményhurokAmikor 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.

3Hívási verem és eseményhurok

Nem kell mélyen érteni, de a fogalmakat ismerd fel, mert a hibaüzenetekben előkerülnek.

HÍVÁSI VEREM itt fut a te kódod olvasFajlt() feldolgoz() main() egyszerre EGY dolog HÁTTÉRMŰVELETEK fájl · hálózat · időzítő a Node végzi, nem a te szálad FELADATSOR az elkészült eredmények itt várakoznak 1. elindít 2. kész 3. visszateszi ESEMÉNYHUROK „üres a verem? akkor jöhet a következő” Amíg a te kódod fut, semmi más nem történik. Amint befejeződik, jöhet a következő kész eredmény.
2. ábra — A körforgás: elindít → háttérben elkészül → visszakerül a verembe.
FogalomMi 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
EBBŐL KÖVETKEZIK EGY FONTOS DOLOG

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.

4A régi módszer: callback

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.

ERROR-FIRST CALLBACK

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.

Miért nem ezt használjuk?

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.

MIÉRT TANULJUK MÉGIS?

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.

5A Promise — „ígéret az eredményre”

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).

pending függőben — még fut sikerült hiba fulfilled teljesült — van eredmény rejected elutasított — hiba történt .then(fv) vagy: az await visszaadja az értéket .catch(fv) vagy: az await hibát dob → try/catch Az állapot EGYSZER változik meg — és nem változik vissza.
3. ábra — A Promise állapotgépe. A .finally() mindkét ág után lefut.

Használat: .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ódusMikor 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

Láncolás

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.

6async / await — a mindennapi eszközöd

Az 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

Ugyanaz háromféleképpen

Nézd meg egymás mellett — mindhárom ugyanazt csinálja:

1) callback
olvasFajlt("a.txt", (hiba, tart) => {
  if (hiba) {
    return console.error(hiba);
  }
  console.log(tart);
});
2) Promise
olvasFajlt("a.txt")
  .then((tart) => {
    console.log(tart);
  })
  .catch((hiba) => {
    console.error(hiba);
  });
3) async / await EZT HASZNÁLJUK
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.

AMIT AZ 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.

Hol használható az await?

// index.js — ESM-modulban ez is működik
const adat = await olvasFajlt("config.json");

Mit ír ki, és milyen sorrendben?

Ö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.

1 / 5Helyes: 0

7Hibakezelés aszinkron kódban

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
  }
}
SorMit 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
A LEGGYAKORIBB FÉLREÉRTÉS A 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.

VESZÉLY — kezeletlen hiba

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ó.

8Sorosan vagy párhuzamosan?

Ez az a rész, ahol az await könnyen lassú kódot eredményez.

SOROSAN — három await egymás után lekeres("A") — 1 mp lekeres("B") — 1 mp lekeres("C") — 1 mp összesen ≈ 3 másodperc PÁRHUZAMOSAN — Promise.all lekeres("A") lekeres("B") lekeres("C") összesen ≈ 1 másodperc mind a három egyszerre indul, az await a leglassabbra vár
4. ábra — Csak akkor sorosan, ha a következő művelethez kell az előző eredménye.

Sorosan — gyakran felesleges

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.

Párhuzamosan — Promise.all

const [a, b, c] = await Promise.all([
  lekeres("A"),
  lekeres("B"),
  lekeres("C"),
]);
// összesen kb. 1 másodperc — a leglassabbé
FIGYELEM

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ódusMit ad vissza?Mikor használd?
Promise.all([…])Az összes eredmény tömbben — vagy az első hibaMinden művelet kell, és mindnek sikerülnie kell
Promise.allSettled([…])Minden elemről egy { status, value / reason } objektumAkkor is kell az eredmény, ha némelyik elhasal
Promise.race([…])A leggyorsabb eredményeIdőkorlát megvalósítása

Sorosan vagy párhuzamosan?

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.

1 / 6Helyes: 0
…

9A négy leggyakoribb kezdő hiba

1) Elfelejtett await

async 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> }
EZ A LEGGYAKORIBB ASZINKRON HIBA

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>.

2) await nem async függvényben

function 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:

ModulrendszerA 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
FIGYELEM — az ESM-es üzenet nem árulja el, mi a baj

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.

3) Felesleges async

async 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.

4) await ciklusban, feleslegesen

❌ lassú — egyesével várja
for (const id of azonositok) {
  const t = await lekeres(id);
  eredmenyek.push(t);
}
✅ gyors — mind elindul
const eredmenyek = await Promise.all(
  azonositok.map((id) => lekeres(id))
);
DE VIGYÁZZ

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.

10Amivel a gyakorlatban találkozol

FeladatHogyan néz ki?
Fájl olvasásaconst t = await fs.readFile("a.txt", "utf8");
HTTP-kérésconst v = await fetch(url); const adat = await v.json();
Várakozásawait 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.

Miért két 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.

EMLÉKEZTETŐ AZ 1. BLOKK 2. FEJEZETÉBŐL

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.

11Feladatok

Elméleti feladatok
F1⭐ alap

Sorrend. Mit ír ki ez a program, és milyen sorrendben? Indokold!

console.log("A");
setTimeout(() => console.log("B"), 0);
console.log("C");
F2⭐ alap

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;
}
F3⭐⭐ haladó

Mit ír ki? Miért, és hogyan javítanád?

async function fo() {
  const t = lekeresTermek(1);
  console.log(t);
}
F4⭐⭐ haladó

Sorosan vagy párhuzamosan? Döntsd el mindegyikről, és indokold:

  • a) Lekérjük a felhasználót, majd az ő rendeléseit
  • b) Lekérjük három különböző termék adatait
  • c) Létrehozunk egy rendelést, majd csökkentjük a készletet
  • d) Lekérjük a főoldalhoz a kiemelt termékeket, a kategóriákat és a hírlevél-szöveget
F5⭐⭐⭐ kihívás

Á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));
Gyakorlati feladatok
G1⭐ alap

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));
}
G2⭐⭐ haladó

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!

G3⭐⭐ haladó

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!

G4⭐⭐⭐ kihívás

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!

12Önellenőrző kvíz

Tíz kérdés. Válassz, és azonnal megtudod, jó volt-e — magyarázattal.

13Összegzés

  1. A JavaScript alapból szinkron, de a lassú műveleteket (fájl, hálózat, adatbázis) aszinkron módon végzi: elindítja, továbbmegy, és később tér vissza az eredményhez.
  2. Az aszinkron nem párhuzamos: a te kódod továbbra is egy szálon fut, csak a várakozás kerül a háttérbe.
  3. A Promise egy később megérkező értéket képvisel; három állapota van: pending, fulfilled, rejected. Az állapot egyszer változik meg.
  4. Az async/await ugyanaz a Promise, olvashatóbb írásmóddal — ezt használjuk mindenhol.
  5. Hibát 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.
  6. Ha a műveletek függetlenek, futtasd őket párhuzamosan Promise.all-lal — ez sokszoros gyorsulást jelenthet.
  7. A leggyakoribb hiba az elfelejtett await. Ismertetőjele: a Promise szó a kimenetben, vagy undefined egy mező helyén.

14Fogalomtár

FogalomJelentés
SzinkronA sorok egymás után futnak; a következő megvárja az előzőt
AszinkronA 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 callbackNode-os minta: a callback első paramétere a hiba
Callback hellEgymásba ágyazott callbackek, jobbra csúszó, olvashatatlan kód
PromiseObjektum, amely egy később megérkező értéket képvisel
pending / fulfilled / rejectedA Promise három állapota: függőben / teljesült / elutasított
asyncMegjelöli a függvényt: Promise-t ad vissza, és await használható benne
awaitMegvá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.allTöbb művelet párhuzamosan; az első hibánál elhasal
Promise.allSettledUgyanaz, de minden elemről megmondja, sikerült-e
Unhandled rejectionKezeletlen Promise-hiba — le is állíthatja a folyamatot
Top-level awaitawait a modul legfelső szintjén, függvényen kívül — ESM-ben működik
← Előző témakör 1/5. VS Code-kiegészítők