Event Loop, Callback, Promise, async/await és hibakezelési minták – a böngészőbeli JavaScript működésének alapjai.
A JavaScript egyszálú (single-threaded) nyelv – egyszerre csak EGY műveletet hajt végre. Ennek ellenére képes aszinkron műveletekre (hálózati kérések, időzítők, felhasználói események) anélkül, hogy blokkolná a felhasználói felületet. Ezt az Event Loop mechanizmus teszi lehetővé.
| Komponens | Feladat | Példa |
|---|---|---|
Call Stack | Szinkron kód végrehajtása (LIFO) | Függvényhívások, változó-hozzárendelés |
Web APIs | Aszinkron műveletek kezelése a böngészőben | setTimeout, fetch, addEventListener |
Macrotask Queue | Aszinkron callback-ek várakozása | setTimeout, setInterval, I/O |
Microtask Queue | Magasabb prioritású aszinkron feladatok | Promise .then(), MutationObserver |
Event Loop | Koordinálja a Stack és a Queue-k közötti munkát | Folyamatos ellenőrzés és áthelyezés |
Promise.then() hamarabb fut, mint a setTimeout(fn, 0).
console.log("1: Szinkron"); setTimeout(function() { console.log("4: setTimeout (Macrotask)"); }, 0); Promise.resolve().then(function() { console.log("3: Promise .then (Microtask)"); }); console.log("2: Szinkron"); // Kimenet: // 1: Szinkron // 2: Szinkron // 3: Promise .then (Microtask) // 4: setTimeout (Macrotask)
A callback egy függvény, amelyet paraméterként adunk át egy másik függvénynek, és az a megfelelő időben meghívja. Ez volt a JavaScript első aszinkron mintája.
// Egyszerű callback: error-first pattern function fetchData(url, callback) { const xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.onload = function() { if (xhr.status === 200) { callback(null, JSON.parse(xhr.responseText)); } else { callback(new Error("HTTP " + xhr.status)); } }; xhr.onerror = function() { callback(new Error("Hálózati hiba")); }; xhr.send(); } // Használat: fetchData("/api/user", function(err, data) { if (err) return console.error(err); console.log(data.name); // "János" });
Ha több aszinkron művelet egymástól függ, a callback-ek egymásba ágyazódnak és a kód olvashatatlanná válik:
✘ Callback Hell getUser(userId, function(err, user) { getOrders(user.id, function(err, orders) { getOrderDetails(orders[0].id, function(err, details) { getProduct(details.productId, function(err, product) { console.log(product.name); // 4 szint mély... és minden szinten // külön hibakezelés kellene! }); }); }); });
A callback hell problémái: mély beágyazás, nehéz hibakezelés (minden szinten külön if (err)), nehéz karbantartás. Ezeket oldja meg a Promise (1.3).
| Állapot | Jelentés | Következő lépés |
|---|---|---|
pending | Folyamatban – még nincs eredmény | Várakozás |
fulfilled | Sikeres – az eredmény elérhető | .then() fut le |
rejected | Sikertelen – hiba történt | .catch() fut le |
const promise = new Promise(function(resolve, reject) { setTimeout(function() { const success = true; if (success) resolve({ name: "János" }); else reject(new Error("Hiba történt")); }, 1000); }); // Láncolás: .then() => .catch() => .finally() promise .then(function(data) { console.log(data.name); }) .catch(function(err) { console.error(err); }) .finally(function() { console.log("Kész."); });
✔ Promise lánc getUser(userId) .then(function(user) { return getOrders(user.id); }) .then(function(orders) { return getOrderDetails(orders[0].id); }) .then(function(details) { return getProduct(details.productId); }) .then(function(product) { console.log(product.name); }) .catch(function(err) { console.error("Hiba:", err); }); // Lapos, egyetlen .catch() az összes hibára!
| Metódus | Viselkedés | Mikor használjuk? |
|---|---|---|
Promise.all() | Mind kell – bármelyik reject => egész reject | Párhuzamos lekérdezések, ahol MIND szükséges |
Promise.allSettled() | Megvárja az összeset, eredménytől függetlenül | Független műveletek, részleges siker is jó |
Promise.race() | Az első befejezett nyer (fulfilled VAGY rejected) | Timeout implementálás |
Promise.any() | Az első SIKERES nyer (rejected-et kihagyja) | Leggyorsabb válaszadó kiválasztása |
// Promise.all – párhuzamos lekérdezés var results = await Promise.all([ fetch("/api/users").then(function(r) { return r.json(); }), fetch("/api/products").then(function(r) { return r.json(); }), fetch("/api/orders").then(function(r) { return r.json(); }), ]); var users = results[0], products = results[1], orders = results[2]; // Mindhárom PÁRHUZAMOSAN fut – nem egymás után!
Az async/await a Promise-ek szinkron KINÉZETŰ kezelését teszi lehetővé. Az async függvény mindig Promise-t ad vissza, az await megvárja a feloldódást.
async function loadDashboard() { var user = await getUser(userId); var orders = await getOrders(user.id); var details = await getOrderDetails(orders[0].id); var product = await getProduct(details.productId); console.log(product.name); }
async function fetchUser(id) { try { var res = await fetch("/api/users/" + id); if (!res.ok) throw new Error("HTTP " + res.status); return await res.json(); } catch (error) { console.error("Hiba:", error.message); showErrorToast(error.message); } finally { hideLoadingSpinner(); } }
✘ Szekvenciális (lassú!) // Egymás UTÁN fut – ha mindegyik 1 mp => 3 mp össz! var users = await fetch("/api/users"); var products = await fetch("/api/products"); var orders = await fetch("/api/orders");
✔ Párhuzamos (gyors!) // PÁRHUZAMOSAN indul mind => max 1 mp! var results = await Promise.all([ fetch("/api/users"), fetch("/api/products"), fetch("/api/orders"), ]);
Promise.all()-t! A szekvenciális await feleslegesen lassítja az alkalmazást.
| Minta | Szintaxis | Előny | Hátrány |
|---|---|---|---|
| Callback | function(err, data) | Egyszerű, régi böngészők | Callback hell, nehéz hibakezelés |
| Promise | .then().catch() | Láncolás, egyetlen .catch() | Még mindig sok .then() |
| async/await | try/catch | Olvasható, szinkron kinézet | ES2017+ kell (ma nem gond) |
// XMLHttpRequest callback mintával var xhr = new XMLHttpRequest(); xhr.open("GET", "/api/data"); xhr.onload = function() { if (xhr.status === 200) { var data = JSON.parse(xhr.responseText); renderData(data); } else { showError("HTTP hiba: " + xhr.status); } }; xhr.onerror = function() { showError("Hálózati hiba"); }; xhr.send();
fetch("/api/data") .then(function(res) { if (!res.ok) throw new Error("HTTP " + res.status); return res.json(); }) .then(function(data) { renderData(data); }) .catch(function(err) { showError(err.message); }) .finally(function() { hideSpinner(); });
async function loadData() { try { var res = await fetch("/api/data"); if (!res.ok) throw new Error("HTTP " + res.status); var data = await res.json(); renderData(data); } catch (err) { showError(err.message); } finally { hideSpinner(); } }
// Nem kezelt hibák elkapása window.addEventListener("error", function(event) { console.error("Globális hiba:", event.message); logToServer({ type: "error", message: event.message }); }); // Nem kezelt Promise rejection window.addEventListener("unhandledrejection", function(event) { console.error("Nem kezelt rejection:", event.reason); event.preventDefault(); logToServer({ type: "rejection", reason: event.reason }); });
.then()/.catch() rövid láncoknál hasznos. A callback minta elavult – csak régebbi API-knál (pl. XMLHttpRequest) találkozunk vele.