A Node.js egyik alapvető előnye a csomagkezelés egyszerűsége, ami lehetővé teszi a fejlesztők számára, hogy gyorsan és hatékonyan kezeljék a projektjeik függőségeit. A npm (Node Package Manager) az egyik legnépszerűbb eszköz erre, és a csomagok telepítése után egy sor fontos művelet történik a háttérben.
A folyamat elején a rendszer letölti a csomagot a meghatározott név és verzió alapján. Az igazi munka akkor kezdődik, amikor a csomag összes függőségét rekurzívan letölti, majd azok függőségeit is. Ez a folyamat addig folytatódik, amíg az összes függőség fa le nem zárul. A letöltött csomagok az új node_modules könyvtárban találhatók, és egy új package-lock.json fájl is keletkezik, amely leírja a node_modules tartalmát.
Ha megnézed a package.json fájlt, észre fogod venni, hogy az új függőség is szerepel benne:
A pontos verziószám, amit ott látsz, valószínűleg újabb lesz, mivel a npm mindig a legfrissebb elérhető verziót használja, amikor a csomagot letölti a npm regisztrációs adatbázisából.
Az importálás módja az, hogy a kódba beillesztjük a következő sort:
Ezután létrehozunk egy alapvető Express.js alkalmazást, amely figyel egy adott porton, és válaszol a / útvonalon érkező kérésekre. A projekt indítása után, ha megnyitjuk a böngészőt és az http://localhost:3000 címet írjuk be, a válasz „Hello, dependencies!” üzenetet kell, hogy lássuk. Ha készen vagyunk, a terminálban a Ctrl+C billentyűkombinációval leállíthatjuk a Node.js alkalmazást.
A telepített csomagok működését követően fontos megérteni a node_modules könyvtár struktúráját és az importálás működését. Node.js-ben háromféle importálás létezik:
-
Az alap Node.js API-k importálása, amelyek tartalmazhatják a
node:előtagot. -
A relatív fájlútvonal használata, például
./vagy../. -
A csomagnevek alapján történő importálás, amely bare specifiernek nevezett formát követ. Ha a csomag neve bare specifierként szerepel, Node.js megkeresi a legközelebbi
node_moduleskönyvtárat, és onnan tölti be a csomagot.
A bare specifierek feloldása alapértelmezett módon történik a node_modules könyvtárban történő kereséssel, de van lehetőség arra is, hogy a Yarn vagy a pnpm csomagkezelők “Plug’n’Play módot” használjanak, ahol a csomagkezelő közvetlenül egy központi gyorsítótárból oldja fel a függőségeket. Ez gyorsítja a build folyamatot, de zavart okozhat az IDE-kben és egyéb eszközökben, amelyek a hagyományos node_modules könyvtárra építenek.
Fontos, hogy a csomagok többszörös verziói is egymás mellett létezhetnek egy projektben, és a csomagok közötti függőségek könnyen összekeveredhetnek. Például, ha a projekted az dep1 és dep2 csomagokat is használja, de az dep1 a lodash@3 verziót, míg az dep2 a lodash@4 verziót igényli, a következő struktúrában lesznek telepítve a csomagok:
Ez úgy működik, hogy a dep1 és dep2 importálásakor a saját node_modules könyvtárukból töltődnek be a megfelelő verziók.
Ha szeretnél saját, helyben található csomagokat importálni, akkor relatív fájlútvonalat kell használnod. Azonban ha más rendszeren található csomagokat szeretnél használni, a symlink (szimbolikus link) használata javasolt. A npm biztosít néhány megoldást erre: ha a projekted egyik függőségekkel kapcsolatos csomag fejlesztésén dolgozol, az npm link parancsot használhatod, hogy egy szimbolikus linket hozz létre a másik csomaghoz a node_modules könyvtárban.
A függőségek szinkronizálása és frissítése szintén fontos része a fejlesztési folyamatnak. A korai npm verziókban a fejlesztők a teljes node_modules könyvtárat a verziókezelőbe (pl. Git) mentették, hogy biztosítsák a függőségek konzisztenciáját, de ez hatalmas fájlméret-növekedést eredményezett. A probléma megoldására a package-lock.json fájl került bevezetésre, amely lehetővé teszi, hogy a projekt pontos másolatát kapjuk, ha a csomagot újra letöltjük és telepítjük.
Ma már a package-lock.json fájl verziókezelésbe való bevitele az ajánlott módszer, míg a node_modules könyvtárat általában nem mentjük verziókezelőbe. A npm install parancs segítségével a rendszer automatikusan helyreállítja a megfelelő függőségeket.
A package-lock.json és a package.json közötti különbség alapvető: míg az előbbi a pontos verziókat határozza meg, addig az utóbbi lehetővé teszi a verziók közötti rugalmasabb választást. A ^ (caret) szimbólum például azt jelzi, hogy a csomaghoz tartozó verziót frissíteni lehet, de nem mehetünk vissza a fő verziótól eltérő változatra.
A függőségek és verziók kezelésének megértése és pontos használata elengedhetetlen a stabil és biztonságos fejlesztési környezet fenntartásához.
Hogyan működnek az arrow függvények és a "this" kontextus a JavaScript-ben?
Az arrow függvények (nyílfunkciók) a JavaScript egyik legfontosabb fejlesztései, mivel egy új megközelítést kínálnak a függvények kontextusának kezelésére. A legfontosabb tulajdonságuk, hogy a "this" kulcsszó értékét a környező kontextus határozza meg, nem pedig a függvény meghívásának módja. Ez a jellemző különösen akkor válik fontossá, amikor különböző típusú függvényeket alkalmazunk, például a normál függvényeket és az arrow függvényeket, amelyek eltérően kezelik a "this" értékét.
Amikor egy arrow függvényt hívunk meg a call vagy apply metódusokkal, a "this" értéke nem változik meg a meghívás során, hanem megőrzi a környező kontextus értékét. Ez rendkívül hasznos lehet olyan helyzetekben, amikor biztosak akarunk lenni abban, hogy a függvény ugyanazt a "this" értéket használja, függetlenül attól, hogy hogyan hívjuk meg.
A bind metódus alkalmazásával az arrow függvények másolatát is létrehozhatjuk, amelynek a "this" értéke megegyezik az eredeti függvénnyel. Ez az úgynevezett lexikai kontextus, amely a JavaScript egyik erőteljes funkciója, mivel lehetővé teszi a típusok pontos előrejelzését a TypeScript-ben is, ami rengeteg problémától kímélhet meg minket.
Az arrow függvények azonban nem minden esetben működnek jól, ha nem megfelelően használjuk őket. Például, ha egy egyszerű JavaScript objektumban próbáljuk használni őket, gyakran elérhetjük, hogy a "this" értéke nem lesz az, amit várnánk. A következő példa jól szemlélteti ezt a problémát:
Ebben az esetben a "this" értéke nem az buttonObject, hanem a környező kontextus lesz, amely teljesen megváltoztathatja a kívánt viselkedést. A legjobb gyakorlat ilyenkor, ha a függvény hívását egy másik arrow függvénybe ágyazzuk:
Ez biztosítja, hogy a clickHandler metódus megfelelő kontextussal rendelkezzen a meghívásakor. Azonban fontos ügyelni arra is, hogy az argumentumokat megfelelően átadjuk a függvényeknek. Ha nem biztosak vagyunk a dolgunkban, használhatjuk a rest paramétereket is:
Ezáltal biztosíthatjuk, hogy minden szükséges adat átadásra kerül. Bár az arrow függvények könnyen alkalmazhatók, fontos, hogy a megfelelő helyen és módon használjuk őket, hogy elkerüljük a hibákat és megőrizzük a kód olvashatóságát.
A "this" kulcsszó a JavaScript-ben implicit függvényparaméterként működik, amely a függvény meghívásának kontextusát reprezentálja. A függvények meghívásának módja döntő hatással van arra, hogy hogyan viselkedik a "this". Az arrow függvények sajátossága, hogy mindig a környező kontextust használják, függetlenül attól, hogy hogyan hívják meg őket. A normál függvények viszont rugalmasabban kezelhetik a "this"-t, és különböző módon viselkedhetnek a különböző meghívások esetén, például ha függvényként, metódusként, konstruktorral vagy a call és apply metódusokkal invokálják őket.
A függvények hívása alapvetően befolyásolja a "this" értékét. Ha egy nem-arrow függvényt függvényként hívunk, a "this" értéke általában undefined lesz, ha szigorú módba (strict mode) kapcsolódunk. Ha metódusként hívunk meg egy függvényt, a "this" értéke az objektum lesz, amelyen a függvény meghívásra került. Konstruktorhíváskor a "this" értéke a létrehozott új objektum lesz. Ha egy függvényt a call vagy apply metódusokkal hívunk, akkor a "this" értéke a hívás első paraméterére vonatkozik.
Fontos megérteni, hogy a JavaScript-ben a függvények viselkedése és a "this" kulcsszó kezelése nem mindig egyszerű, és sokszor okozhat váratlan eredményeket, ha nem vesszük figyelembe a kontextust. Az arrow függvények segítenek a kódunk tisztaságának megőrzésében, de helyes használatukhoz alapos megértésre van szükség.
Hogyan működik a Promise láncolás és a párhuzamos műveletek kezelése?
A Promise objektumok kezelése kulcsfontosságú a modern JavaScript aszinkron programozásában. Azonban ahhoz, hogy teljes mértékben kihasználjuk a promise-k erejét, fontos, hogy megértsük, hogyan működnek azok az ígéretek, amelyek már teljesítve lettek, és hogyan alkalmazhatjuk őket hatékonyan különböző szituációkban, például láncoláskor vagy párhuzamos műveletek végrehajtásakor.
A promise-ok akkor tekinthetők "teljesítve" vagy "elutasítva", amikor már nem fogadják el újabb hívásokat a resolve (teljesítés) vagy reject (elutasítás) függvények. A könyvünkben a "settled" kifejezés fogja leírni azokat a promise-okat, amelyek már teljesítettek, vagy elutasítottak. A hétköznapi beszédben azonban gyakran halljuk, hogy valaki "resolve"-ot mond, miközben valójában a "settled"-et vagy a "fulfilled"-et (teljesített) érti. A resolve(thenable) funkciót gyakran használják rövidítéseként a thenable.then(resolve, reject) kifejezésnek. Fontos, hogy tisztában legyünk egy fontos különbséggel: ha a resolve és reject más részekről hívhatóak, akkor a resolve(thenable) hívása csendben meghiúsul, mivel egy ígéretet csak egyszer lehet teljesíteni. Érdemes megjegyezni, hogy bár a resolve és reject függvények átadása az executor-ban nem jelenti azt, hogy ezeket ténylegesen az executor-ból kell hívni. A fejlesztők különböző trükköket alkalmaztak, hogy lehetővé tegyék a promise-ok külső részekből történő teljesítését. Például:
A legújabb ES2024 szabvány azonban bevezette a Promise.withResolvers() statikus metódust, amely pontosan ezt a helyzetet kezeli: egy új ígéretet ad vissza, amely tartalmazza a resolve és reject függvényeket:
Ez a technika egyszerű hídként szolgál a promise-ok és callback-alapú API-k között. Így létrehozhatunk egy ígéretet, amely az API hívásának eredményét képviseli, ha a resolve függvényt callback-ként adjuk át.
Miután megértettük, hogyan történik az ígéret teljesítése, érdemes rátérni a promise láncolásának egyszerű, de hatékony módjára.
A Promise láncolás és a then() metódus
A then() metódus lehetővé teszi, hogy a promise-okhoz callbacks-okat adjunk hozzá, amelyeket az ígéret teljesítése után futtathatunk le. Azonban a then()-nek van egy fontos trükkje: egy új ígéretet ad vissza. Ez az új ígéret a kezdettől fogva "pending" (függő) állapotban van, és az adott callback függvény viselkedésének megfelelően válik teljesítetté. Ha a callback egy értéket ad vissza, az új ígéret a visszaadott értékkel lesz teljesítve. Ez az érték visszatérési logika ugyanazokat a szabályokat követi, mint amit a resolve függvénynél láttunk.
A Promise láncolásának ereje abban rejlik, hogy képesek vagyunk más ígéreteket visszaadni a then() callback-jéből. Így a láncolás segítségével sorozatos aszinkron műveleteket hajthatunk végre, és mindegyik művelet eredménye az előző művelet eredményére építhet. Ezt a gyakorlatban a fetch() API példájával mutathatjuk be:
Ez a kód példát ad arra, hogyan lehet a láncolás segítségével kezelni egy aszinkron API hívás eredményét. A catch() metódus lehetővé teszi, hogy az összes lehetséges hibát egy központi helyen kezeljük, és így elkerüljük a callback hell-t, miközben az összes hibát kezelhetjük egyetlen helyen.
Párhuzamos műveletek kezelése Promise-okkal
A Promise láncolásának egyik erőssége, hogy segítségével egyszerűsíthetjük a sorozatos aszinkron műveletek kezelését. De mi a helyzet a párhuzamos műveletekkel? Mi van, ha több aszinkron hívást szeretnénk párhuzamosan elvégezni? A Promise objektum a Promise.all() statikus metódust kínál, amely lehetővé teszi több ígéret együttes kezelését. A következő példa három különböző API hívást hajt végre párhuzamosan, és várja meg azok eredményeit:
A Promise.all() minden ígéretet párhuzamosan indít el, és ha mindegyik sikeresen teljesült, az eredményeket egy tömbben adja vissza. Ha bármelyik ígéret elutasításra kerül, a Promise.all azonnal elutasítja az egész láncot. Azonban, ha nagyobb rugalmasságra van szükségünk, az ES2020 szabvány bevezette a Promise.allSettled() metódust, amely akkor is visszatér, ha az ígéretek nem mindegyike teljesült, és a lánc minden műveletének állapotát és eredményét tartalmazza.
A Promise.any() és Promise.race() metódusok más alternatívákat kínálnak, amelyek lehetővé teszik az első sikeres ígéret figyelembevételét, vagy a legelső befejeződött művelet eredményét.
Fontos, hogy a promise-ok használata során mindig ügyeljünk arra, hogy a hiba kezelését megfelelően végezzük el. A catch() és finally() metódusok használata mellett figyelni kell arra is, hogy ne hagyjunk figyelmen kívül elutasított ígéreteket, mivel azokat könnyen figyelmen kívül hagyhatjuk, ha nem kezeljük őket explicit módon.
Hogyan használjuk az aszinkron függvényeket és generátorokat JavaScript-ben
A JavaScript aszinkron programozása az utóbbi években jelentős változásokon ment keresztül, amelyek megkönnyítik a fejlesztők számára az aszinkron műveletek kezelését. Az egyik legnagyobb előrelépés az async és await kulcsszavak bevezetése volt, amelyek az aszinkron kód írását intuitívabbá és olvashatóbbá teszik. A fetch API például az aszinkron műveletek kezelésére kifejezetten hasznos, különösen JSON adatok lekérésénél és feldolgozásánál. Azonban a generátorok, amelyek először az ES2015-ben jelentek meg, egy másik hasznos technikát kínálnak a JavaScript-ben, különösen, ha a fejlesztők egy több lépésből álló, megszakítható ciklusokat szeretnének létrehozni.
Az aszinkron függvények és a Promise kezelés
Az async és await kulcsszavak használata lehetővé teszi számunkra, hogy aszinkron függvényeket deklaráljunk, és így képesek legyünk a promiszokat szinkron módon kezelni. Az async kulcsszóval deklarált függvények garantálják, hogy mindig egy Promise típusú értéket adnak vissza. Az await kulcsszó használata ezen függvények belsejében lehetővé teszi a program futásának megszakítását, amíg a promise nem oldódik fel, anélkül hogy a többi művelet futását blokkolná.
Egy egyszerű példa a fetch API használatával:
Ebben a példában látható, hogy az aszinkron kód mennyire tiszta és egyszerű, különösen a callback funkciókkal ellentétben, amelyek bonyolultabbak és nehezebben karbantarthatók. A try-catch blokk segítségével könnyen kezelhetjük a hibákat, miközben az aszinkron folyamatokat szinkron módon dolgozzuk fel. A Node.js például számos beépített modullal rendelkezik, amelyek támogatják az aszinkron működést, például az fs/promises modullal, amely az fs modul aszinkron változata.
Generátorok és iterálható objektumok
A JavaScript generátorokat az ES2015-ben vezették be, és bár kezdetben nagy figyelmet kaptak, a legtöbb fejlesztő számára a generátorok szintaxisa és használata túl bonyolultnak tűnt. Azonban a generátorok egy speciális esetben hasznosak lehetnek, különösen ha olyan alkalmazásokat fejlesztünk, amelyek több lépésből álló, megszakítható iterációkat igényelnek.
A generátorok segítségével egy függvény yield kulcsszóval képes értékeket visszaadni, miközben fenntartja az aktuális állapotát. Az iterátorok olyan objektumok, amelyek rendelkeznek egy next() metódussal, amely az iteráció következő elemét adja vissza. A generátorok így lehetővé teszik a fejlesztők számára, hogy kényelmes módon kezeljék az iterációkat anélkül, hogy bonyolult logikát kellene írniuk.
Iterálható objektumok és generátorok
A JavaScript-ben az iterálható objektumok olyan objektumok, amelyek rendelkeznek egy Symbol.iterator kulccsal. Az iterálható objektumokat használhatjuk a for...of ciklussal, amely az összes olyan objektumot képes iterálni, amelyek teljesítik az iterálhatóság feltételeit. Az iterátorok egy next() metódust biztosítanak, amely visszaadja az aktuális értéket és egy done nevű booleant, amely jelzi, hogy az iteráció befejeződött-e.
A generátorok és az iterátorok szoros kapcsolatban állnak egymással, mivel a generátorok mindig iterálható objektumokat adnak vissza. Az alábbi kód például egy egyszerű generátor funkciót mutat be, amely számos URL-t kér le és dolgoz fel:
Ebben az esetben a generátor egy iterációt biztosít, amely minden URL-hez egy új fetch kérésindítást ad. A generátorok alkalmazása akkor hasznos, ha több lépésből álló aszinkron műveletek láncolására van szükség, és a fejlesztők nem szeretnék, hogy az alkalmazás futása blokkolódjon.
Iterátorok és destrukturálás
A JavaScript-ben az iterálható objektumokat destrukturálás segítségével könnyen felbonthatjuk és feldolgozhatjuk. Az ES2015-ben bevezetett destrukturálás szintaxisa lehetővé teszi, hogy egy iterálható objektum elemeit közvetlenül változókba rendeljük. Ezt például egy tömbvel is megtehetjük:
Ez a szintaxis kényelmes módja annak, hogy az iterálható objektumokat átalakítsuk és feldolgozzuk őket. Az iterálható objektumok nemcsak tömbök, hanem bármilyen más olyan objektumok is lehetnek, amelyek megfelelnek az iterálhatóság feltételeinek.
A JavaScript-es iterátorok és generátorok hatékonyan segítik az aszinkron műveletek és a több lépésből álló iterációk kezelését, így fontos, hogy a fejlesztők tisztában legyenek ezekkel a technikákkal, és alkalmazzák őket a megfelelő helyeken.
Miért fontos a típusellenőrzés a JavaScript-ben és hogyan segít a TypeScript?
A programozók gyakran elkövetnek egy egyszerű hibát, amit könnyen meg lehet oldani, ha jobban odafigyelünk a típusok kezelésére. Vegyünk egy példát: ha eltévesztjük az argumentumok sorrendjét a setTimeout függvény használatakor:
Ha ezt a kódot egy böngészőben futtatjuk, érdekes dolog történik: semmi. Nem keletkezik hibaüzenet, és a fejlesztői konzolon sem jelenik meg semmi. Sok böngésző API ilyen, ami megnehezíti a hibák nyomon követését, amelyek a sikertelen időzítő miatt keletkeznek. A Node.js viszont szigorúbb: a hibás setTimeout futtatása futásidejű hibát eredményez, és egy informatív üzenetet ad. De miért kellene várni a futásig, hogy egy olyan hibát fedezzünk fel, amit már a kód szemrevételezésével is észrevehettünk volna?
A JavaScript története során számos próbálkozás volt arra, hogy megoldják ezt a problémát. A legtöbb megoldás a JavaScript egy teljesen más nyelvből generálásával próbálkozott. Például a Google GWT a Java kódot JavaScript alkalmazássá fordítja. A TypeScript más megközelítést alkalmazott: nem egy teljesen különálló nyelv, hanem egy JavaScript-nyelv kiterjesztése, amely lehetővé teszi a típusok ellenőrzését fordítási időben.
Mivel a TypeScript a JavaScript kiterjesztése, a fenti JavaScript kód TypeScript-ben is érvényes. Azonban, bár a TypeScript "csak" JavaScript, a típusellenőrző képes észlelni a hibát. Ezt könnyen ellenőrizhetjük, ha bemásoljuk a kódot a TypeScript Playground-ba: https://www.typescriptlang.org/play.
A következő lépésben egy Node.js projektet fogunk létrehozni TypeScript-tel.
A típusos nyelvek típusai
Amikor a fejlesztők panaszkodnak, hogy a JavaScript nem rendelkezik megfelelő típusokkal, két különböző koncepciót kell megkülönböztetni: Az egyik probléma az, hogy a JavaScript dinamikusan típusos nyelv, ami azt jelenti, hogy a típusok csak futásidőben érvényesülnek. Egy statikusan típusos nyelven a típusok fordítási időben érvényesülnek. A TypeScript statikusan típusos nyelv, mert a típusellenőrzőt a kód futtatása előtt végezhetjük el.
A másik probléma a JavaScript implicit típuskonverziója. Például az 1 + "2" kifejezés eredménye a string "12", mivel minden, ami egy stringhez hozzáadódik, automatikusan stringgé alakul JavaScript-ben. A TypeScript ugyanúgy viselkedik, mint a JavaScript futásidőben, így a típuskonverzió továbbra is problémát jelenthet. Azonban a TypeScript típusellenőrzője képes előre jelezni a típuskonverzió eredményét, így ha például az 1 + "2" kifejezést olyan kontextusban használjuk, ahol nem várunk stringet, hibát dob.
A TypeScript fordító telepítése és futtatása
Most létrehozunk egy új JavaScript projektet. Hozzunk létre egy új mappát, typescript-project néven, és adjunk hozzá egy package.json fájlt. Ehhez nem szükséges sok információ, mivel ezt a fájlt csak a függőségek nyomon követésére használjuk. Egyszerűen beilleszthetjük az üres JSON objektumot:
Ezután az npm segítségével telepítjük a legújabb TypeScript csomagot fejlesztői függőségként:
A TypeScript csomag tartalmaz egy tsc nevű futtatható fájlt, amely a TypeScript fordítót jelenti. Mielőtt elkezdenénk a TypeScript fordítást, konfigurálni kell a projektet. A tsc egy tsconfig.json fájlt keres, amely tartalmazza a szükséges információkat, például hogy mely TypeScript fájlokat fordítson, milyen típusokat kell importálni, milyen szigorú legyen a típusellenőrzés és így tovább.
A npx tsc --init parancs segítségével a tsc létrehozhat egy alap konfigurációs fájlt. Az alábbi konfigurációval:
Ezzel létrejön egy új tsconfig.json fájl, amely tartalmazza a szükséges beállításokat, mint például a célnyelvet és a szigorú típusellenőrzést.
Típusdeklarációk és annotációk
A következő kódrészlet egy egyszerű segédfüggvényt definiál, amely pluralizálja a szavakat. Ha elmentjük egy pluralize.ts nevű fájlba, akkor TypeScript hibát ad, mivel a paraméterek típusai nincsenek meghatározva:
A fenti kód teljesen érvényes JavaScript, de ha ezt TypeScript-ben futtatjuk, hibát fogunk kapni:
Hibaüzenet:
Ez azt jelenti, hogy a str és count paraméterek típusai nincsenek meghatározva, ezért a TypeScript automatikusan any típusúként kezeli őket. A probléma elkerülése érdekében érdemes típusokat hozzárendelni a paraméterekhez:
Ezzel a változtatással a TypeScript képes lesz érdemben ellenőrizni a kódot, és hibát ad, ha valami nem illeszkedik a várt típushoz.
A típusellenőrzés és típusdeklarációk használata biztosítja, hogy a kód előre meghatározott típusokkal és struktúrával dolgozzon, így elkerülhetők a futásidőbeli hibák, és a kód könnyebben karbantarthatóvá válik.
Hogyan figyeljük meg és kontrolláljuk a kémiai reakciókat különböző technikák alkalmazásával?
Hogyan befolyásolja a formális asszimiláció a bevándorlók társadalmi elfogadását és támogatását?
Hogyan készítsünk erős és tartós íjfonalat természetes anyagokból?

Deutsch
Francais
Nederlands
Svenska
Norsk
Dansk
Suomi
Espanol
Italiano
Portugues
Magyar
Polski
Cestina
Русский